From 2ecbf1bb65ffa3de59921bd818f5cd49efe25882 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 21 Jun 2018 14:49:19 -0400 Subject: [PATCH] Fix 'contact cell vs. message details layout' issue. --- Signal.xcodeproj/project.pbxproj | 8 + Signal/src/Signal-Bridging-Header.h | 1 + .../MessageDetailViewController.swift | 14 +- .../ViewControllers/NewGroupViewController.m | 4 - .../ShowGroupMembersViewController.m | 4 +- Signal/src/views/GroupTableViewCell.swift | 6 +- SignalMessaging/Views/ContactCellView.h | 32 ++ SignalMessaging/Views/ContactCellView.m | 277 +++++++++++++ SignalMessaging/Views/ContactTableViewCell.h | 20 +- SignalMessaging/Views/ContactTableViewCell.m | 373 +----------------- .../contacts/SelectThreadViewController.m | 2 - 11 files changed, 357 insertions(+), 384 deletions(-) create mode 100644 SignalMessaging/Views/ContactCellView.h create mode 100644 SignalMessaging/Views/ContactCellView.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 73df8b416..7facedb81 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -212,6 +212,8 @@ 34D1F0BA1F8800D90066283D /* OWSAudioMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0B91F8800D90066283D /* OWSAudioMessageView.m */; }; 34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0BC1F8D108C0066283D /* AttachmentUploadView.m */; }; 34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0BF1F8EC1760066283D /* MessageRecipientStatusUtils.swift */; }; + 34D2015120DC160E00A6FD3A /* ContactCellView.h in Headers */ = {isa = PBXBuildFile; fileRef = 34D2014F20DC160D00A6FD3A /* ContactCellView.h */; }; + 34D2015220DC160E00A6FD3A /* ContactCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2015020DC160D00A6FD3A /* ContactCellView.m */; }; 34D2CCD220618B3000CB1A14 /* OWSBackupLazyRestoreJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD120618B2F00CB1A14 /* OWSBackupLazyRestoreJob.swift */; }; 34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */; }; 34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDB206939B100CB1A14 /* DebugUIMessagesAction.m */; }; @@ -858,6 +860,8 @@ 34D1F0BB1F8D108C0066283D /* AttachmentUploadView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AttachmentUploadView.h; sourceTree = ""; }; 34D1F0BC1F8D108C0066283D /* AttachmentUploadView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentUploadView.m; sourceTree = ""; }; 34D1F0BF1F8EC1760066283D /* MessageRecipientStatusUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRecipientStatusUtils.swift; sourceTree = ""; }; + 34D2014F20DC160D00A6FD3A /* ContactCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContactCellView.h; path = SignalMessaging/Views/ContactCellView.h; sourceTree = SOURCE_ROOT; }; + 34D2015020DC160D00A6FD3A /* ContactCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContactCellView.m; path = SignalMessaging/Views/ContactCellView.m; sourceTree = SOURCE_ROOT; }; 34D2CCD120618B2F00CB1A14 /* OWSBackupLazyRestoreJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupLazyRestoreJob.swift; sourceTree = ""; }; 34D2CCD82062E7D000CB1A14 /* OWSScreenLockUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSScreenLockUI.h; sourceTree = ""; }; 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSScreenLockUI.m; sourceTree = ""; }; @@ -1595,6 +1599,8 @@ children = ( 45F3AEB51DFDE7900080CE33 /* AvatarImageView.swift */, 346129D11FD2085A00532771 /* CommonStrings.swift */, + 34D2014F20DC160D00A6FD3A /* ContactCellView.h */, + 34D2015020DC160D00A6FD3A /* ContactCellView.m */, 340CB2221EAC155C0001CAA1 /* ContactsViewHelper.h */, 340CB2231EAC155C0001CAA1 /* ContactsViewHelper.m */, 76EB052E18170B33006006FC /* ContactTableViewCell.h */, @@ -2431,6 +2437,7 @@ 346129951FD1E30000532771 /* OWSDatabaseMigration.h in Headers */, 45194F961FD7226300333B2C /* SelectThreadViewController.h in Headers */, 346129B41FD1F7E800532771 /* OWSProfileManager.h in Headers */, + 34D2015120DC160E00A6FD3A /* ContactCellView.h in Headers */, 346129FA1FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.h in Headers */, 346129E21FD5C0BE00532771 /* VersionMigrations.h in Headers */, 34480B611FD0A98800BC14EF /* UIColor+OWS.h in Headers */, @@ -3154,6 +3161,7 @@ 451F8A381FD7117E005CB9DA /* OWSViewController.m in Sources */, 450C801120AD1CDB00F3A091 /* ReturnToCallViewController.swift in Sources */, 450C800C20AD191E00F3A091 /* OWSNavigationController.m in Sources */, + 34D2015220DC160E00A6FD3A /* ContactCellView.m in Sources */, 346129721FD1D74C00532771 /* SignalKeyingStorage.m in Sources */, 34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */, 459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */, diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 9fa139969..51adce28b 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -7,6 +7,7 @@ // Separate iOS Frameworks from other imports. #import "AppSettingsViewController.h" +#import "ContactCellView.h" #import "ContactTableViewCell.h" #import "ConversationViewItem.h" #import "DateUtil.h" diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index 878b46f7d..ceb30e981 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -238,13 +238,15 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele addDivider() } - let contentView = UIView() - contentView.layoutMargins = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20) - let cell = ContactTableViewCell(customContentView: contentView) + // We use ContactCellView, not ContactTableViewCell. + // Table view cells don't layout properly outside the + // context of a table view. + let cellView = ContactCellView() + cellView.layoutMargins = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20) // We use the "short" status message to avoid being redundant with the section title. - cell.accessoryMessage = shortStatusMessage - cell.configure(withRecipientId: recipientId, contactsManager: self.contactsManager) - groupRows.append(contentView) + cellView.accessoryMessage = shortStatusMessage + cellView.configure(withRecipientId: recipientId, contactsManager: self.contactsManager) + groupRows.append(cellView) } if groupRows.count > 0 { diff --git a/Signal/src/ViewControllers/NewGroupViewController.m b/Signal/src/ViewControllers/NewGroupViewController.m index 20b67be6e..2e1a73c5e 100644 --- a/Signal/src/ViewControllers/NewGroupViewController.m +++ b/Signal/src/ViewControllers/NewGroupViewController.m @@ -236,8 +236,6 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68; } else if (isBlocked) { cell.accessoryMessage = NSLocalizedString( @"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked."); - } else { - OWSAssert(cell.accessoryMessage == nil); } if (signalAccount) { @@ -333,8 +331,6 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68; } else if (isBlocked) { cell.accessoryMessage = NSLocalizedString( @"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked."); - } else { - OWSAssert(cell.accessoryMessage == nil); } [cell configureWithSignalAccount:signalAccount diff --git a/Signal/src/ViewControllers/ThreadSettings/ShowGroupMembersViewController.m b/Signal/src/ViewControllers/ThreadSettings/ShowGroupMembersViewController.m index 414f844b4..7a006f84a 100644 --- a/Signal/src/ViewControllers/ThreadSettings/ShowGroupMembersViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/ShowGroupMembersViewController.m @@ -202,9 +202,9 @@ NS_ASSUME_NONNULL_BEGIN } if (isVerified) { - cell.subtitle.attributedText = cell.verifiedSubtitle; + [cell setAttributedSubtitle:cell.verifiedSubtitle]; } else { - cell.subtitle.attributedText = nil; + [cell setAttributedSubtitle:nil]; } return cell; diff --git a/Signal/src/views/GroupTableViewCell.swift b/Signal/src/views/GroupTableViewCell.swift index 861baa300..c2272913c 100644 --- a/Signal/src/views/GroupTableViewCell.swift +++ b/Signal/src/views/GroupTableViewCell.swift @@ -39,10 +39,10 @@ import SignalServiceKit avatarView.autoPinLeadingToSuperviewMargin() avatarView.autoVCenterInSuperview() - avatarView.autoSetDimension(.width, toSize: CGFloat(kContactTableViewCellAvatarSize)) + avatarView.autoSetDimension(.width, toSize: CGFloat(kContactCellAvatarSize)) avatarView.autoPinToSquareAspectRatio() - textContainer.autoPinEdge(.leading, to: .trailing, of: avatarView, withOffset: kContactTableViewCellAvatarTextMargin) + textContainer.autoPinEdge(.leading, to: .trailing, of: avatarView, withOffset: kContactCellAvatarTextMargin) textContainer.autoPinTrailingToSuperviewMargin() textContainer.autoVCenterInSuperview() } @@ -65,7 +65,7 @@ import SignalServiceKit }.joined(separator: ", ") self.subtitleLabel.text = groupMemberNames - self.avatarView.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: kContactTableViewCellAvatarSize, contactsManager: contactsManager) + self.avatarView.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: kContactCellAvatarSize, contactsManager: contactsManager) } } diff --git a/SignalMessaging/Views/ContactCellView.h b/SignalMessaging/Views/ContactCellView.h new file mode 100644 index 000000000..8e9d95a19 --- /dev/null +++ b/SignalMessaging/Views/ContactCellView.h @@ -0,0 +1,32 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +extern const NSUInteger kContactCellAvatarSize; +extern const CGFloat kContactCellAvatarTextMargin; + +@class OWSContactsManager; +@class SignalAccount; +@class TSThread; + +@interface ContactCellView : UIView + +@property (nonatomic, nullable) NSString *accessoryMessage; + +- (void)configureWithSignalAccount:(SignalAccount *)signalAccount contactsManager:(OWSContactsManager *)contactsManager; + +- (void)configureWithRecipientId:(NSString *)recipientId contactsManager:(OWSContactsManager *)contactsManager; + +- (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager; + +- (void)prepareForReuse; + +- (NSAttributedString *)verifiedSubtitle; + +- (void)setAttributedSubtitle:(nullable NSAttributedString *)attributedSubtitle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/Views/ContactCellView.m b/SignalMessaging/Views/ContactCellView.m new file mode 100644 index 000000000..fd88dbff7 --- /dev/null +++ b/SignalMessaging/Views/ContactCellView.m @@ -0,0 +1,277 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "ContactCellView.h" +#import "OWSContactAvatarBuilder.h" +#import "OWSContactsManager.h" +#import "UIFont+OWS.h" +#import "UIView+OWS.h" +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +const NSUInteger kContactCellAvatarSize = 48; +const CGFloat kContactCellAvatarTextMargin = 12; + +@interface ContactCellView () + +@property (nonatomic) UILabel *nameLabel; +@property (nonatomic) UILabel *profileNameLabel; +@property (nonatomic) UIImageView *avatarView; +@property (nonatomic) UILabel *subtitleLabel; +@property (nonatomic) UILabel *ows_accessoryView; +@property (nonatomic) UIStackView *nameContainerView; + +@property (nonatomic) OWSContactsManager *contactsManager; +@property (nonatomic) NSString *recipientId; + +@end + +#pragma mark - + +@implementation ContactCellView + +- (instancetype)init +{ + if (self = [super init]) { + [self configure]; + } + return self; +} + +- (void)configure +{ + OWSAssert(!self.nameLabel); + + self.layoutMargins = UIEdgeInsetsZero; + + _avatarView = [AvatarImageView new]; + [_avatarView autoSetDimension:ALDimensionWidth toSize:kContactCellAvatarSize]; + [_avatarView autoSetDimension:ALDimensionHeight toSize:kContactCellAvatarSize]; + + self.nameLabel = [UILabel new]; + self.nameLabel.lineBreakMode = NSLineBreakByTruncatingTail; + self.nameLabel.textColor = [UIColor blackColor]; + + self.profileNameLabel = [UILabel new]; + self.profileNameLabel.lineBreakMode = NSLineBreakByTruncatingTail; + self.profileNameLabel.textColor = [UIColor grayColor]; + + self.subtitleLabel = [UILabel new]; + self.subtitleLabel.textColor = [UIColor ows_darkGrayColor]; + + self.ows_accessoryView = [[UILabel alloc] init]; + self.ows_accessoryView.textAlignment = NSTextAlignmentRight; + self.ows_accessoryView.textColor = [UIColor colorWithWhite:0.5f alpha:1.f]; + + self.nameContainerView = [[UIStackView alloc] initWithArrangedSubviews:@[ + self.nameLabel, + self.profileNameLabel, + self.subtitleLabel, + ]]; + self.nameContainerView.axis = UILayoutConstraintAxisVertical; + self.nameContainerView.alignment = UIStackViewAlignmentFill; + + UIStackView *hStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + self.avatarView, + self.nameContainerView, + self.ows_accessoryView, + ]]; + hStackView.axis = UILayoutConstraintAxisHorizontal; + hStackView.spacing = kContactCellAvatarTextMargin; + hStackView.distribution = UIStackViewDistributionFill; + [self addSubview:hStackView]; + [hStackView autoVCenterInSuperview]; + [hStackView autoPinLeadingToSuperviewMargin]; + [hStackView autoPinTrailingToSuperviewMargin]; + // Ensure that the cell's contents never overflow the cell bounds. + [hStackView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; + [hStackView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; + + [self configureFonts]; +} + +- (void)configureFonts +{ + self.nameLabel.font = [UIFont ows_dynamicTypeBodyFont]; + self.profileNameLabel.font = [UIFont ows_regularFontWithSize:11.f]; + self.subtitleLabel.font = [UIFont ows_regularFontWithSize:11.f]; + self.ows_accessoryView.font = [UIFont ows_mediumFontWithSize:13.f]; +} + +- (void)configureWithSignalAccount:(SignalAccount *)signalAccount contactsManager:(OWSContactsManager *)contactsManager +{ + [self configureWithRecipientId:signalAccount.recipientId contactsManager:contactsManager]; +} + +- (void)configureWithRecipientId:(NSString *)recipientId contactsManager:(OWSContactsManager *)contactsManager +{ + OWSAssert(recipientId.length > 0); + OWSAssert(contactsManager); + + // Update fonts to reflect changes to dynamic type. + [self configureFonts]; + + self.recipientId = recipientId; + self.contactsManager = contactsManager; + + self.nameLabel.attributedText = + [contactsManager formattedFullNameForRecipientId:recipientId font:self.nameLabel.font]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(otherUsersProfileDidChange:) + name:kNSNotificationName_OtherUsersProfileDidChange + object:nil]; + [self updateProfileName]; + [self updateAvatar]; + + if (self.accessoryMessage) { + self.ows_accessoryView.text = self.accessoryMessage; + } + + // Force layout, since imageView isn't being initally rendered on App Store optimized build. + [self layoutSubviews]; +} + +- (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager +{ + OWSAssert(thread); + + // Update fonts to reflect changes to dynamic type. + [self configureFonts]; + + self.contactsManager = contactsManager; + + NSString *threadName = thread.name; + if (threadName.length == 0 && [thread isKindOfClass:[TSGroupThread class]]) { + threadName = [MessageStrings newGroupDefaultTitle]; + } + + NSAttributedString *attributedText = + [[NSAttributedString alloc] initWithString:threadName + attributes:@{ + NSForegroundColorAttributeName : [UIColor blackColor], + }]; + self.nameLabel.attributedText = attributedText; + + if ([thread isKindOfClass:[TSContactThread class]]) { + self.recipientId = thread.contactIdentifier; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(otherUsersProfileDidChange:) + name:kNSNotificationName_OtherUsersProfileDidChange + object:nil]; + [self updateProfileName]; + } + self.avatarView.image = + [OWSAvatarBuilder buildImageForThread:thread diameter:kContactCellAvatarSize contactsManager:contactsManager]; + + if (self.accessoryMessage) { + self.ows_accessoryView.text = self.accessoryMessage; + } + + // Force layout, since imageView isn't being initally rendered on App Store optimized build. + [self layoutSubviews]; +} + +- (void)updateAvatar +{ + OWSContactsManager *contactsManager = self.contactsManager; + if (contactsManager == nil) { + OWSFail(@"%@ contactsManager should not be nil", self.logTag); + self.avatarView.image = nil; + return; + } + + NSString *recipientId = self.recipientId; + if (recipientId.length == 0) { + OWSFail(@"%@ recipientId should not be nil", self.logTag); + self.avatarView.image = nil; + return; + } + + self.avatarView.image = [[[OWSContactAvatarBuilder alloc] initWithSignalId:recipientId + diameter:kContactCellAvatarSize + contactsManager:contactsManager] build]; +} + +- (void)updateProfileName +{ + OWSContactsManager *contactsManager = self.contactsManager; + if (contactsManager == nil) { + OWSFail(@"%@ contactsManager should not be nil", self.logTag); + self.profileNameLabel.text = nil; + return; + } + + NSString *recipientId = self.recipientId; + if (recipientId.length == 0) { + OWSFail(@"%@ recipientId should not be nil", self.logTag); + self.profileNameLabel.text = nil; + return; + } + + if ([contactsManager hasNameInSystemContactsForRecipientId:recipientId]) { + // Don't display profile name when we have a veritas name in system Contacts + self.profileNameLabel.text = nil; + } else { + // Use profile name, if any is available + self.profileNameLabel.text = [contactsManager formattedProfileNameForRecipientId:recipientId]; + } + + [self.profileNameLabel setNeedsLayout]; +} + +- (void)prepareForReuse +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + self.accessoryMessage = nil; + self.nameLabel.text = nil; + self.subtitleLabel.text = nil; + self.profileNameLabel.text = nil; + self.ows_accessoryView.text = nil; +} + +- (void)otherUsersProfileDidChange:(NSNotification *)notification +{ + OWSAssertIsOnMainThread(); + + NSString *recipientId = notification.userInfo[kNSNotificationKey_ProfileRecipientId]; + OWSAssert(recipientId.length > 0); + + if (recipientId.length > 0 && [self.recipientId isEqualToString:recipientId]) { + [self updateProfileName]; + [self updateAvatar]; + } +} + +- (NSAttributedString *)verifiedSubtitle +{ + NSMutableAttributedString *text = [NSMutableAttributedString new]; + // "checkmark" + [text appendAttributedString:[[NSAttributedString alloc] + initWithString:@"\uf00c " + attributes:@{ + NSFontAttributeName : + [UIFont ows_fontAwesomeFont:self.subtitleLabel.font.pointSize], + }]]; + [text appendAttributedString:[[NSAttributedString alloc] + initWithString:NSLocalizedString(@"PRIVACY_IDENTITY_IS_VERIFIED_BADGE", + @"Badge indicating that the user is verified.")]]; + return [text copy]; +} + +- (void)setAttributedSubtitle:(nullable NSAttributedString *)attributedSubtitle +{ + self.subtitleLabel.attributedText = attributedSubtitle; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/Views/ContactTableViewCell.h b/SignalMessaging/Views/ContactTableViewCell.h index 718512bbe..b51d18cfd 100644 --- a/SignalMessaging/Views/ContactTableViewCell.h +++ b/SignalMessaging/Views/ContactTableViewCell.h @@ -4,36 +4,28 @@ #import "OWSContactsManager.h" -/** - * - * ContactTableViewCell displays a contact from a Contact object. - * - */ - NS_ASSUME_NONNULL_BEGIN -extern const NSUInteger kContactTableViewCellAvatarSize; -extern const CGFloat kContactTableViewCellAvatarTextMargin; - @class OWSContactsManager; @class SignalAccount; @class TSThread; @interface ContactTableViewCell : UITableViewCell -@property (nonatomic, nullable) NSString *accessoryMessage; -@property (nonatomic, readonly) UILabel *subtitle; - + (NSString *)reuseIdentifier; -- (instancetype)initWithCustomContentView:(UIView *)customContentView; - - (void)configureWithSignalAccount:(SignalAccount *)signalAccount contactsManager:(OWSContactsManager *)contactsManager; - (void)configureWithRecipientId:(NSString *)recipientId contactsManager:(OWSContactsManager *)contactsManager; - (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager; +// This method should be called _before_ the configure... methods. +- (void)setAccessoryMessage:(nullable NSString *)accessoryMessage; + +// This method should be called _after_ the configure... methods. +- (void)setAttributedSubtitle:(nullable NSAttributedString *)attributedSubtitle; + - (NSAttributedString *)verifiedSubtitle; @end diff --git a/SignalMessaging/Views/ContactTableViewCell.m b/SignalMessaging/Views/ContactTableViewCell.m index 9920fca29..15c3e7022 100644 --- a/SignalMessaging/Views/ContactTableViewCell.m +++ b/SignalMessaging/Views/ContactTableViewCell.m @@ -3,36 +3,16 @@ // #import "ContactTableViewCell.h" -#import "Environment.h" -#import "OWSContactAvatarBuilder.h" -#import "OWSContactsManager.h" -#import "OWSUserProfile.h" +#import "ContactCellView.h" #import "UIFont+OWS.h" -#import "UIUtil.h" #import "UIView+OWS.h" -#import #import -#import -#import -#import NS_ASSUME_NONNULL_BEGIN -const NSUInteger kContactTableViewCellAvatarSize = 48; -const CGFloat kContactTableViewCellAvatarTextMargin = 12; - @interface ContactTableViewCell () -@property (nonatomic) UILabel *nameLabel; -@property (nonatomic) UILabel *profileNameLabel; -@property (nonatomic) UIImageView *avatarView; -@property (nonatomic) UILabel *subtitle; -@property (nonatomic) UILabel *ows_accessoryView; -@property (nonatomic) UIStackView *nameContainerView; -//@property (nonatomic) UIView *nameContainerView; - -@property (nonatomic) OWSContactsManager *contactsManager; -@property (nonatomic) NSString *recipientId; +@property (nonatomic) ContactCellView *cellView; @end @@ -43,17 +23,7 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12; - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier { if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { - [self configureWithContentView:self.contentView]; - } - return self; -} - -- (instancetype)initWithCustomContentView:(UIView *)customContentView -{ - if (self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ContactTableViewCell.reuseIdentifier]) { - OWSAssert(customContentView); - - [self configureWithContentView:customContentView]; + [self configure]; } return self; } @@ -68,366 +38,63 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12; OWSFail(@"%@ don't use accessory view for this view.", self.logTag); } -- (void)configureWithContentView:(UIView *)contentView +- (void)configure { - // self.preservesSuperviewLayoutMargins = YES; - // self.contentView.preservesSuperviewLayoutMargins = YES; - // - OWSAssert(!self.nameLabel); - - // self.contentView.translatesAutoresizingMaskIntoConstraints = NO; - // self.translatesAutoresizingMaskIntoConstraints = YES; - // self.contentView.translatesAutoresizingMaskIntoConstraints = YES; - // self.translatesAutoresizingMaskIntoConstraints = NO; - // self.contentView.translatesAutoresizingMaskIntoConstraints = NO; - - _avatarView = [AvatarImageView new]; - [_avatarView autoSetDimension:ALDimensionWidth toSize:kContactTableViewCellAvatarSize]; - [_avatarView autoSetDimension:ALDimensionHeight toSize:kContactTableViewCellAvatarSize]; - - self.nameLabel = [UILabel new]; - self.nameLabel.lineBreakMode = NSLineBreakByTruncatingTail; - self.nameLabel.textColor = [UIColor blackColor]; - - self.profileNameLabel = [UILabel new]; - self.profileNameLabel.lineBreakMode = NSLineBreakByTruncatingTail; - self.profileNameLabel.textColor = [UIColor grayColor]; - - self.subtitle = [UILabel new]; - self.subtitle.textColor = [UIColor ows_darkGrayColor]; - - self.ows_accessoryView = [[UILabel alloc] init]; - self.ows_accessoryView.textAlignment = NSTextAlignmentRight; - self.ows_accessoryView.textColor = [UIColor colorWithWhite:0.5f alpha:1.f]; - - // self.nameContainerView = self.nameLabel; + OWSAssert(!self.cellView); - // self.nameContainerView = [UIView containerView]; - // [self.nameContainerView addSubview:self.nameLabel]; - // [self.nameContainerView addSubview:self.profileNameLabel]; - // [self.nameContainerView addSubview:self.subtitle]; - // [self.nameLabel autoPinWidthToSuperview]; - // [self.profileNameLabel autoPinWidthToSuperview]; - // [self.subtitle autoPinWidthToSuperview]; - // [self.nameLabel autoPinTopToSuperviewMargin]; - // [self.profileNameLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.nameLabel]; - // [self.subtitle autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.profileNameLabel]; - // [self.subtitle autoPinBottomToSuperviewMargin]; - // - // [contentView addSubview:self.avatarView]; - // [contentView addSubview:self.nameContainerView]; - // [contentView addSubview:self.ows_accessoryView]; - // - // [self.avatarView autoVCenterInSuperview]; - // [self.nameContainerView autoVCenterInSuperview]; - // [self.ows_accessoryView autoVCenterInSuperview]; - // [self.avatarView autoPinLeadingToSuperviewMargin]; - // [self.nameContainerView autoPinLeadingToTrailingEdgeOfView:self.avatarView - // offset:kContactTableViewCellAvatarTextMargin]; - //// [self.nameContainerView autoPinEdgeToSuperviewEdge:ALEdgeTrailing]; - //// [self.nameContainerView autoPinTrailingToSuperviewMargin]; - // [self.ows_accessoryView autoPinLeadingToTrailingEdgeOfView:self.nameContainerView - // offset:kContactTableViewCellAvatarTextMargin]; [self.ows_accessoryView autoPinTrailingToSuperviewMargin]; - // // Ensure that the cell's contents never overflow the cell bounds. - // [self.avatarView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; - // [self.avatarView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; - // [self.nameContainerView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; - // [self.nameContainerView autoPinEdgeToSuperviewMargin:ALEdgeBottom - // relation:NSLayoutRelationGreaterThanOrEqual]; [self.ows_accessoryView autoPinEdgeToSuperviewMargin:ALEdgeTop - // relation:NSLayoutRelationGreaterThanOrEqual]; [self.ows_accessoryView - // autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; - - // - //// UIView h = [UIView containerView]; - //// [self.nameContainerView addSubview:self.nameLabel]; - //// [self.nameContainerView addSubview:self.profileNameLabel]; - //// [self.nameContainerView addSubview:self.subtitle]; - //// [self.nameLabel autoPinWidthToSuperview]; - //// [self.profileNameLabel autoPinWidthToSuperview]; - //// [self.subtitle autoPinWidthToSuperview]; - //// [self.nameLabel autoPinTopToSuperviewMargin]; - //// [self.profileNameLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.nameLabel]; - //// [self.subtitle autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.profileNameLabel]; - //// [self.subtitle autoPinBottomToSuperviewMargin]; - // - - self.nameContainerView = [[UIStackView alloc] initWithArrangedSubviews:@[ - self.nameLabel, - self.profileNameLabel, - self.subtitle, - ]]; - self.nameContainerView.axis = UILayoutConstraintAxisVertical; - self.nameContainerView.alignment = UIStackViewAlignmentFill; - // hStackView.distribution = UIStackViewDistributionFill; - // [self.contentView addSubview:hStackView]; - // [hStackView autoVCenterInSuperview]; - // [hStackView autoPinLeadingToSuperviewMargin]; - // [hStackView autoPinTrailingToSuperviewMargin]; - // // Ensure that the cell's contents never overflow the cell bounds. - // [hStackView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; - // [hStackView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; - // - // - //// [self.avatarView setContentHuggingHorizontalHigh]; - //// [self.nameLabel setContentHuggingHorizontalLow]; - //// [self.profileNameLabel setContentHuggingHorizontalLow]; - //// [self.subtitle setContentHuggingHorizontalLow]; - //// [self.nameContainerView setContentHuggingHorizontalLow]; - // - // [self.ows_accessoryView setContentHuggingHorizontalLow]; - // - //// UIView *hStackView = [UIView new]; - //// hStackView.backgroundColor = UIColor.greenColor; - //// [self.contentView addSubview:hStackView]; - //// [hStackView autoPinToSuperviewEdges]; - //// [hStackView setContentHuggingLow]; - // - //// [hStackView autoVCenterInSuperview]; - //// [hStackView autoPinLeadingToSuperviewMargin]; - //// // [hStackView autoPinTrailingToSuperviewMargin]; - //// [hStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing]; - //// // Ensure that the cell's contents never overflow the cell bounds. - //// [hStackView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; - //// [hStackView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; - //// [hStackView setContentHuggingHorizontalLow]; - // - UIStackView *hStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ - self.avatarView, - self.nameContainerView, - self.ows_accessoryView, - ]]; - hStackView.axis = UILayoutConstraintAxisHorizontal; - hStackView.spacing = kContactTableViewCellAvatarTextMargin; - hStackView.distribution = UIStackViewDistributionFill; - [contentView addSubview:hStackView]; - [hStackView autoVCenterInSuperview]; - [hStackView autoPinLeadingToSuperviewMargin]; - [hStackView autoPinTrailingToSuperviewMargin]; - // [hStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing]; - // Ensure that the cell's contents never overflow the cell bounds. - [hStackView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; - [hStackView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; - // [hStackView setContentHuggingHorizontalLow]; - // [hStackView setCompressionResistanceHorizontalLow]; - // [hStackView addBackgroundViewWithBackgroundColor:[UIColor greenColor]]; - // [self.nameContainerView addBackgroundViewWithBackgroundColor:[UIColor blueColor]]; - - [self configureFonts]; - - // // Force layout, since imageView isn't being initally rendered on App Store optimized build. - // [self layoutSubviews]; - // - // [self logFrameLaterWithLabel:@"cell"]; - // [self.contentView logFrameLaterWithLabel:@"contentView"]; - // [self.avatarView logFrameLaterWithLabel:@"avatarView"]; - // [self.nameContainerView logFrameLaterWithLabel:@"nameContainerView"]; -} - -- (void)configureFonts -{ - self.nameLabel.font = [UIFont ows_dynamicTypeBodyFont]; - self.profileNameLabel.font = [UIFont ows_regularFontWithSize:11.f]; - self.subtitle.font = [UIFont ows_regularFontWithSize:11.f]; - self.ows_accessoryView.font = [UIFont ows_mediumFontWithSize:13.f]; + self.cellView = [ContactCellView new]; + [self.contentView addSubview:self.cellView]; + [self.cellView autoPinEdgesToSuperviewMargins]; } - (void)configureWithSignalAccount:(SignalAccount *)signalAccount contactsManager:(OWSContactsManager *)contactsManager { - [self configureWithRecipientId:signalAccount.recipientId contactsManager:contactsManager]; + [self.cellView configureWithRecipientId:signalAccount.recipientId contactsManager:contactsManager]; } - (void)configureWithRecipientId:(NSString *)recipientId contactsManager:(OWSContactsManager *)contactsManager { - OWSAssert(recipientId.length > 0); - OWSAssert(contactsManager); - - // Update fonts to reflect changes to dynamic type. - [self configureFonts]; - - self.recipientId = recipientId; - self.contactsManager = contactsManager; - - self.nameLabel.attributedText = - [contactsManager formattedFullNameForRecipientId:recipientId font:self.nameLabel.font]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(otherUsersProfileDidChange:) - name:kNSNotificationName_OtherUsersProfileDidChange - object:nil]; - [self updateProfileName]; - [self updateAvatar]; - - if (self.accessoryMessage) { - self.ows_accessoryView.text = self.accessoryMessage; - } + [self.cellView configureWithRecipientId:recipientId contactsManager:contactsManager]; // Force layout, since imageView isn't being initally rendered on App Store optimized build. [self layoutSubviews]; - - DDLogVerbose(@"%@ nameLabel size: %@, %@", - self.logTag, - NSStringFromCGSize([self.nameLabel sizeThatFits:CGSizeZero]), - NSStringFromCGSize([self.nameLabel intrinsicContentSize])); - DDLogVerbose(@"%@ nameContainerView size: %@, %@", - self.logTag, - NSStringFromCGSize([self.nameContainerView sizeThatFits:CGSizeZero]), - NSStringFromCGSize([self.nameContainerView intrinsicContentSize])); - DDLogVerbose(@"%@ ows_accessoryView size: %@, %@", - self.logTag, - NSStringFromCGSize([self.ows_accessoryView sizeThatFits:CGSizeZero]), - NSStringFromCGSize([self.ows_accessoryView intrinsicContentSize])); - DDLogVerbose(@"%@ contentView size: %@, %@", - self.logTag, - NSStringFromCGSize([self.contentView sizeThatFits:CGSizeZero]), - NSStringFromCGSize([self.contentView intrinsicContentSize])); - - // [self.nameLabel sizeToFit]; - // [self.nameLabel autoSetDimension:ALDimensionWidth toSize:self.nameLabel.width]; - // [self.nameLabel autoSetDimension:ALDimensionHeight toSize:self.nameLabel.height]; - - // [self.nameContainerView sizeToFit]; - // [self.nameContainerView.superview sizeToFit]; - // [self.nameContainerView logFrameWithLabel:@"nameContainerView?"]; - - [self logFrameLaterWithLabel:@"cell"]; - [self.contentView logFrameLaterWithLabel:@"contentView"]; - [self.avatarView logFrameLaterWithLabel:@"avatarView"]; - [self.nameContainerView logFrameLaterWithLabel:@"nameContainerView"]; - [self.ows_accessoryView logFrameLaterWithLabel:@"ows_accessoryView"]; } - (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager { OWSAssert(thread); - // Update fonts to reflect changes to dynamic type. - [self configureFonts]; - - self.contactsManager = contactsManager; - - NSString *threadName = thread.name; - if (threadName.length == 0 && [thread isKindOfClass:[TSGroupThread class]]) { - threadName = [MessageStrings newGroupDefaultTitle]; - } - - NSAttributedString *attributedText = - [[NSAttributedString alloc] initWithString:threadName - attributes:@{ - NSForegroundColorAttributeName : [UIColor blackColor], - }]; - self.nameLabel.attributedText = attributedText; + [self.cellView configureWithThread:thread contactsManager:contactsManager]; - if ([thread isKindOfClass:[TSContactThread class]]) { - self.recipientId = thread.contactIdentifier; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(otherUsersProfileDidChange:) - name:kNSNotificationName_OtherUsersProfileDidChange - object:nil]; - [self updateProfileName]; - } - self.avatarView.image = [OWSAvatarBuilder buildImageForThread:thread - diameter:kContactTableViewCellAvatarSize - contactsManager:contactsManager]; - - if (self.accessoryMessage) { - self.ows_accessoryView.text = self.accessoryMessage; - } - // Force layout, since imageView isn't being initally rendered on App Store optimized build. [self layoutSubviews]; } -- (NSAttributedString *)verifiedSubtitle +- (void)setAccessoryMessage:(nullable NSString *)accessoryMessage { - NSMutableAttributedString *text = [NSMutableAttributedString new]; - // "checkmark" - [text appendAttributedString:[[NSAttributedString alloc] - initWithString:@"\uf00c " - attributes:@{ - NSFontAttributeName : - [UIFont ows_fontAwesomeFont:self.subtitle.font.pointSize], - }]]; - [text appendAttributedString:[[NSAttributedString alloc] - initWithString:NSLocalizedString(@"PRIVACY_IDENTITY_IS_VERIFIED_BADGE", - @"Badge indicating that the user is verified.")]]; - return [text copy]; + OWSAssert(self.cellView); + + self.cellView.accessoryMessage = accessoryMessage; } -- (void)updateAvatar +- (NSAttributedString *)verifiedSubtitle { - OWSContactsManager *contactsManager = self.contactsManager; - if (contactsManager == nil) { - OWSFail(@"%@ contactsManager should not be nil", self.logTag); - self.avatarView.image = nil; - return; - } - - NSString *recipientId = self.recipientId; - if (recipientId.length == 0) { - OWSFail(@"%@ recipientId should not be nil", self.logTag); - self.avatarView.image = nil; - return; - } - - self.avatarView.image = [[[OWSContactAvatarBuilder alloc] initWithSignalId:recipientId - diameter:kContactTableViewCellAvatarSize - contactsManager:contactsManager] build]; + return self.cellView.verifiedSubtitle; } -- (void)updateProfileName +- (void)setAttributedSubtitle:(nullable NSAttributedString *)attributedSubtitle { - OWSContactsManager *contactsManager = self.contactsManager; - if (contactsManager == nil) { - OWSFail(@"%@ contactsManager should not be nil", self.logTag); - self.profileNameLabel.text = nil; - return; - } - - NSString *recipientId = self.recipientId; - if (recipientId.length == 0) { - OWSFail(@"%@ recipientId should not be nil", self.logTag); - self.profileNameLabel.text = nil; - return; - } - - if ([contactsManager hasNameInSystemContactsForRecipientId:recipientId]) { - // Don't display profile name when we have a veritas name in system Contacts - self.profileNameLabel.text = nil; - } else { - // Use profile name, if any is available - self.profileNameLabel.text = [contactsManager formattedProfileNameForRecipientId:recipientId]; - } - - [self.profileNameLabel setNeedsLayout]; + [self.cellView setAttributedSubtitle:attributedSubtitle]; } - (void)prepareForReuse { [super prepareForReuse]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self.cellView prepareForReuse]; - self.accessoryMessage = nil; self.accessoryView = nil; self.accessoryType = UITableViewCellAccessoryNone; - self.nameLabel.text = nil; - self.subtitle.text = nil; - self.profileNameLabel.text = nil; - self.ows_accessoryView.text = nil; -} - -- (void)otherUsersProfileDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - NSString *recipientId = notification.userInfo[kNSNotificationKey_ProfileRecipientId]; - OWSAssert(recipientId.length > 0); - - if (recipientId.length > 0 && [self.recipientId isEqualToString:recipientId]) { - [self updateProfileName]; - [self updateAvatar]; - } } @end diff --git a/SignalMessaging/contacts/SelectThreadViewController.m b/SignalMessaging/contacts/SelectThreadViewController.m index a14e2d5c4..be7b92642 100644 --- a/SignalMessaging/contacts/SelectThreadViewController.m +++ b/SignalMessaging/contacts/SelectThreadViewController.m @@ -282,8 +282,6 @@ NS_ASSUME_NONNULL_BEGIN if (isBlocked) { cell.accessoryMessage = NSLocalizedString( @"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked."); - } else { - OWSAssert(cell.accessoryMessage == nil); } [cell configureWithSignalAccount:signalAccount contactsManager:helper.contactsManager]; return cell;