From f4a559156cd517595aec4c492eccc19ea23963dd Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 12 Jun 2018 11:27:32 -0400 Subject: [PATCH 1/7] Style the search results. --- Signal/src/Signal-Bridging-Header.h | 2 + .../ConversationSearchViewController.swift | 216 +++++------------- .../ViewControllers/HomeView/HomeViewCell.h | 8 +- .../ViewControllers/HomeView/HomeViewCell.m | 84 ++++--- .../HomeView/HomeViewController.h | 4 + .../HomeView/HomeViewController.m | 19 +- .../NewContactThreadViewController.m | 3 + SignalMessaging/Views/ContactTableViewCell.h | 5 +- SignalMessaging/Views/ContactTableViewCell.m | 34 ++- .../utils/ConversationSearcher.swift | 4 +- 10 files changed, 167 insertions(+), 212 deletions(-) diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index df9d0ad23..9dbf6c231 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -7,11 +7,13 @@ // Separate iOS Frameworks from other imports. #import "AppSettingsViewController.h" +#import "ContactTableViewCell.h" #import "ConversationViewItem.h" #import "DateUtil.h" #import "DebugUIPage.h" #import "DebugUITableViewController.h" #import "FingerprintViewController.h" +#import "HomeViewCell.h" #import "HomeViewController.h" #import "MediaDetailViewController.h" #import "NotificationSettingsViewController.h" diff --git a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift index 24b1a7c4d..778367716 100644 --- a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift +++ b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift @@ -29,8 +29,21 @@ class ConversationSearchViewController: UITableViewController { case messages } + var contactsManager: OWSContactsManager { + return Environment.current().contactsManager + } + + var blockedPhoneNumberSet = Set() + // MARK: View Lifecyle + override public func loadView() { + super.loadView() + + let blockingManager = OWSBlockingManager.shared() + blockedPhoneNumberSet = Set(blockingManager.blockedPhoneNumbers()) + } + override func viewDidLoad() { super.viewDidLoad() @@ -38,9 +51,8 @@ class ConversationSearchViewController: UITableViewController { tableView.estimatedRowHeight = 60 tableView.register(EmptySearchResultCell.self, forCellReuseIdentifier: EmptySearchResultCell.reuseIdentifier) - tableView.register(ConversationSearchResultCell.self, forCellReuseIdentifier: ConversationSearchResultCell.reuseIdentifier) - tableView.register(MessageSearchResultCell.self, forCellReuseIdentifier: MessageSearchResultCell.reuseIdentifier) - tableView.register(ContactSearchResultCell.self, forCellReuseIdentifier: ContactSearchResultCell.reuseIdentifier) + tableView.register(HomeViewCell.self, forCellReuseIdentifier: HomeViewCell.cellReuseIdentifier()) + tableView.register(ContactTableViewCell.self, forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier()) } // MARK: UITableViewDelegate @@ -131,7 +143,7 @@ class ConversationSearchViewController: UITableViewController { cell.configure(searchText: searchText) return cell case .conversations: - guard let cell = tableView.dequeueReusableCell(withIdentifier: ConversationSearchResultCell.reuseIdentifier) as? ConversationSearchResultCell else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: HomeViewCell.cellReuseIdentifier()) as? HomeViewCell else { owsFail("cell was unexpectedly nil") return UITableViewCell() } @@ -140,10 +152,10 @@ class ConversationSearchViewController: UITableViewController { owsFail("searchResult was unexpectedly nil") return UITableViewCell() } - cell.configure(searchResult: searchResult) + cell.configure(withThread: searchResult.thread, contactsManager: contactsManager, blockedPhoneNumber: self.blockedPhoneNumberSet) return cell case .contacts: - guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactSearchResultCell.reuseIdentifier) as? ContactSearchResultCell else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactTableViewCell.reuseIdentifier()) as? ContactTableViewCell else { owsFail("cell was unexpectedly nil") return UITableViewCell() } @@ -152,11 +164,10 @@ class ConversationSearchViewController: UITableViewController { owsFail("searchResult was unexpectedly nil") return UITableViewCell() } - - cell.configure(searchResult: searchResult) + cell.configure(with: searchResult.signalAccount, contactsManager: contactsManager) return cell case .messages: - guard let cell = tableView.dequeueReusableCell(withIdentifier: MessageSearchResultCell.reuseIdentifier) as? MessageSearchResultCell else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: HomeViewCell.cellReuseIdentifier()) as? HomeViewCell else { owsFail("cell was unexpectedly nil") return UITableViewCell() } @@ -166,7 +177,44 @@ class ConversationSearchViewController: UITableViewController { return UITableViewCell() } - cell.configure(searchResult: searchResult) + var overrideSnippet = NSAttributedString() + var overrideTimestamp: NSNumber? + self.uiDatabaseConnection.read { transaction in + guard let messageId = searchResult.messageId else { + owsFail("\(ConversationSearchViewController.logTag) message search result is missing message id") + return + } + guard let message = TSInteraction.fetch(uniqueId: messageId, transaction: transaction) else { + // This could happen if the message has disappeared, etc. + Logger.error("\(ConversationSearchViewController.logTag) could not load message") + return + } + overrideTimestamp = NSNumber(value: message.timestamp) + + guard let snippet = searchResult.snippet else { + owsFail("\(ConversationSearchViewController.logTag) message search result is missing snippet") + return + } + guard let snippetData = snippet.data(using: String.Encoding.utf8) else { + owsFail("\(ConversationSearchViewController.logTag) message search result has invalid snippet") + return + } + do { + let snippetOptions = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html] + overrideSnippet = try NSAttributedString(data: snippetData, + options: snippetOptions, + documentAttributes: nil) + } catch { + owsFail("\(ConversationSearchViewController.logTag) could not parse message snippet") + } + } + + cell.configure(withThread: searchResult.thread, + contactsManager: contactsManager, + blockedPhoneNumber: self.blockedPhoneNumberSet, + overrideSnippet: overrideSnippet, + overrideTimestamp: overrideTimestamp) + return cell } } @@ -227,154 +275,6 @@ class ConversationSearchViewController: UITableViewController { } } -class ConversationSearchResultCell: UITableViewCell { - static let reuseIdentifier = "ConversationSearchResultCell" - - let nameLabel: UILabel - let snippetLabel: UILabel - let avatarView: AvatarImageView - let avatarWidth: UInt = 40 - - override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - self.nameLabel = UILabel() - self.snippetLabel = UILabel() - self.avatarView = AvatarImageView() - avatarView.autoSetDimensions(to: CGSize(width: CGFloat(avatarWidth), height: CGFloat(avatarWidth))) - - super.init(style: style, reuseIdentifier: reuseIdentifier) - - nameLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() - snippetLabel.font = UIFont.ows_dynamicTypeFootnote - - let textRows = UIStackView(arrangedSubviews: [nameLabel, snippetLabel]) - textRows.axis = .vertical - - let columns = UIStackView(arrangedSubviews: [avatarView, textRows]) - columns.axis = .horizontal - columns.spacing = 8 - - contentView.addSubview(columns) - columns.autoPinEdgesToSuperviewMargins() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - var contactsManager: OWSContactsManager { - return Environment.current().contactsManager - } - - func configure(searchResult: ConversationSearchResult) { - self.avatarView.image = OWSAvatarBuilder.buildImage(thread: searchResult.thread.threadRecord, diameter: avatarWidth, contactsManager: self.contactsManager) - self.nameLabel.text = searchResult.thread.name - self.snippetLabel.text = searchResult.snippet - } -} - -class MessageSearchResultCell: UITableViewCell { - static let reuseIdentifier = "MessageSearchResultCell" - - let nameLabel: UILabel - let snippetLabel: UILabel - - override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - self.nameLabel = UILabel() - self.snippetLabel = UILabel() - - super.init(style: style, reuseIdentifier: reuseIdentifier) - - nameLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() - snippetLabel.font = UIFont.ows_dynamicTypeFootnote - - let textRows = UIStackView(arrangedSubviews: [nameLabel, snippetLabel]) - textRows.axis = .vertical - - contentView.addSubview(textRows) - textRows.autoPinEdgesToSuperviewMargins() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configure(searchResult: ConversationSearchResult) { - self.nameLabel.text = searchResult.thread.name - - guard let snippet = searchResult.snippet else { - self.snippetLabel.text = nil - return - } - - guard let encodedString = snippet.data(using: .utf8) else { - self.snippetLabel.text = nil - return - } - - // Bold snippet text - do { - - // FIXME - The snippet marks up the matched search text with tags. - // We can parse this into an attributed string, but it also takes on an undesirable font. - // We want to apply our own font without clobbering bold in the process - maybe by enumerating and inspecting the attributes? Or maybe we can pass in a base font? - let attributedSnippet = try NSMutableAttributedString(data: encodedString, - options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html], - documentAttributes: nil) - attributedSnippet.addAttribute(NSAttributedStringKey.font, value: self.snippetLabel.font, range: NSRange(location: 0, length: attributedSnippet.length)) - - self.snippetLabel.attributedText = attributedSnippet - } catch { - owsFail("failed to generate snippet: \(error)") - } - } -} - -class ContactSearchResultCell: UITableViewCell { - static let reuseIdentifier = "ContactSearchResultCell" - - let nameLabel: UILabel - let snippetLabel: UILabel - let avatarView: AvatarImageView - let avatarWidth: UInt = 40 - - override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - self.nameLabel = UILabel() - self.snippetLabel = UILabel() - self.avatarView = AvatarImageView() - avatarView.autoSetDimensions(to: CGSize(width: CGFloat(avatarWidth), height: CGFloat(avatarWidth))) - - super.init(style: style, reuseIdentifier: reuseIdentifier) - - nameLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() - snippetLabel.font = UIFont.ows_dynamicTypeFootnote - - let textRows = UIStackView(arrangedSubviews: [nameLabel, snippetLabel]) - textRows.axis = .vertical - - let columns = UIStackView(arrangedSubviews: [avatarView, textRows]) - columns.axis = .horizontal - columns.spacing = 8 - - contentView.addSubview(columns) - columns.autoPinEdgesToSuperviewMargins() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - var contactsManager: OWSContactsManager { - return Environment.current().contactsManager - } - - func configure(searchResult: ContactSearchResult) { - let avatarBuilder = OWSContactAvatarBuilder.init(signalId: searchResult.recipientId, diameter: avatarWidth, contactsManager: contactsManager) - self.avatarView.image = avatarBuilder.build() - self.nameLabel.text = self.contactsManager.displayName(forPhoneIdentifier: searchResult.recipientId) - self.snippetLabel.text = searchResult.recipientId - } -} - class EmptySearchResultCell: UITableViewCell { static let reuseIdentifier = "EmptySearchResultCell" diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.h b/Signal/src/ViewControllers/HomeView/HomeViewCell.h index 054b9fcb5..c715ba40d 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.h +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.h @@ -10,14 +10,18 @@ NS_ASSUME_NONNULL_BEGIN @interface HomeViewCell : UITableViewCell -+ (CGFloat)rowHeight; - + (NSString *)cellReuseIdentifier; - (void)configureWithThread:(ThreadViewModel *)thread contactsManager:(OWSContactsManager *)contactsManager blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet; +- (void)configureWithThread:(ThreadViewModel *)thread + contactsManager:(OWSContactsManager *)contactsManager + blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet + overrideSnippet:(nullable NSAttributedString *)overrideSnippet + overrideTimestamp:(nullable NSNumber *)overrideTimestamp; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index 283f35887..16c4dc067 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -59,6 +59,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(!self.avatarView); + const CGFloat kMinVMargin = 5; + [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.layoutMargins = UIEdgeInsetsMake(0, self.cellHMargin, 0, self.cellHMargin); self.contentView.layoutMargins = UIEdgeInsetsZero; @@ -127,6 +129,16 @@ NS_ASSUME_NONNULL_BEGIN [self.unreadLabel autoCenterInSuperview]; [self.unreadLabel setContentHuggingHigh]; [self.unreadLabel setCompressionResistanceHigh]; + + // Ensure that the cell's contents never overflow the cell bounds. + // We pin pin to the superview _edge_ and not _margin_ for the purposes + // of overflow, so that changes to the margins do not trip these safe guards. + [self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeTop + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; + [self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeBottom + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; } + (NSString *)cellReuseIdentifier @@ -147,6 +159,19 @@ NS_ASSUME_NONNULL_BEGIN - (void)configureWithThread:(ThreadViewModel *)thread contactsManager:(OWSContactsManager *)contactsManager blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet +{ + [self configureWithThread:thread + contactsManager:contactsManager + blockedPhoneNumberSet:blockedPhoneNumberSet + overrideSnippet:nil + overrideTimestamp:nil]; +} + +- (void)configureWithThread:(ThreadViewModel *)thread + contactsManager:(OWSContactsManager *)contactsManager + blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet + overrideSnippet:(nullable NSAttributedString *)overrideSnippet + overrideTimestamp:(nullable NSNumber *)overrideTimestamp { OWSAssertIsOnMainThread(); OWSAssert(thread); @@ -168,13 +193,22 @@ NS_ASSUME_NONNULL_BEGIN self.payloadView.spacing = 0.f; self.topRowView.spacing = self.topRowHSpacing; + if (overrideSnippet) { + self.snippetLabel.attributedText = overrideSnippet; + } else { + self.snippetLabel.attributedText = + [self attributedSnippetForThread:thread blockedPhoneNumberSet:blockedPhoneNumberSet]; + } // We update the fonts every time this cell is configured to ensure that // changes to the dynamic type settings are reflected. + // + // Note: we apply this font _after_ we set the attributed text to + // override any font attributes. self.snippetLabel.font = [self snippetFont]; - self.snippetLabel.attributedText = - [self attributedSnippetForThread:thread blockedPhoneNumberSet:blockedPhoneNumberSet]; - self.dateTimeLabel.text = [self stringForDate:thread.lastMessageDate]; + self.dateTimeLabel.text = (overrideTimestamp + ? [self stringForDate:[NSDate ows_dateWithMillisecondsSince1970:overrideTimestamp.unsignedLongLongValue]] + : [self stringForDate:thread.lastMessageDate]); if (hasUnreadMessages) { self.dateTimeLabel.textColor = [UIColor ows_blackColor]; @@ -203,19 +237,22 @@ NS_ASSUME_NONNULL_BEGIN // Spec check. Should be 12pts (6pt on each side) when using default font size. OWSAssert(UIFont.ows_dynamicTypeBodyFont.pointSize != 17 || minMargin == 12); - [self.viewConstraints addObject:[self.unreadBadge autoMatchDimension:ALDimensionWidth - toDimension:ALDimensionWidth - ofView:self.unreadLabel - withOffset:minMargin]]; + [self.viewConstraints addObjectsFromArray:@[ + [self.unreadBadge autoMatchDimension:ALDimensionWidth + toDimension:ALDimensionWidth + ofView:self.unreadLabel + withOffset:minMargin], + // badge sizing + [self.unreadBadge autoSetDimension:ALDimensionWidth + toSize:unreadBadgeHeight + relation:NSLayoutRelationGreaterThanOrEqual], + [self.unreadBadge autoSetDimension:ALDimensionHeight toSize:unreadBadgeHeight], + ]]; + }]; + const CGFloat kMinVMargin = 5; [self.viewConstraints addObjectsFromArray:@[ - // badge sizing - [self.unreadBadge autoSetDimension:ALDimensionWidth - toSize:unreadBadgeHeight - relation:NSLayoutRelationGreaterThanOrEqual], - [self.unreadBadge autoSetDimension:ALDimensionHeight toSize:unreadBadgeHeight], - // Horizontally, badge is inserted after the tail of the payloadView, pushing back the date *and* snippet // view [self.payloadView autoPinEdge:ALEdgeTrailing @@ -223,6 +260,12 @@ NS_ASSUME_NONNULL_BEGIN ofView:self.unreadBadge withOffset:-self.topRowHSpacing], [self.unreadBadge autoPinTrailingToSuperviewMargin], + [self.unreadBadge autoPinEdgeToSuperviewEdge:ALEdgeTop + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual], + [self.unreadBadge autoPinEdgeToSuperviewEdge:ALEdgeBottom + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual], // Vertically, badge is positioned vertically by aligning it's label *subview's* baseline. // This allows us a single visual baseline of text across the top row across [name, dateTime, @@ -367,21 +410,6 @@ NS_ASSUME_NONNULL_BEGIN return minValue * alpha; } -+ (CGFloat)rowHeight -{ - // Scale the cell height using size of dynamic "body" type as a reference. - const CGFloat kReferenceFontSizeMin = 17.f; - const CGFloat kReferenceFontSizeMax = 23.f; - CGFloat referenceFontSize = UIFont.ows_dynamicTypeBodyFont.pointSize; - CGFloat alpha = CGFloatClamp01(CGFloatInverseLerp(referenceFontSize, kReferenceFontSizeMin, kReferenceFontSizeMax)); - - const CGFloat kCellHeightMin = 68.f; - const CGFloat kCellHeightMax = 80.f; - CGFloat result = ceil(CGFloatLerp(kCellHeightMin, kCellHeightMax, alpha)); - - return result; -} - - (NSUInteger)cellHMargin { return 16; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.h b/Signal/src/ViewControllers/HomeView/HomeViewController.h index d86824b5b..821c34a85 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.h +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.h @@ -6,6 +6,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @class TSThread; @interface HomeViewController : OWSViewController @@ -25,3 +27,5 @@ animatePresentation:(BOOL)animatePresentation; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index fb4d3c048..f4e7ec4aa 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -31,6 +31,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + typedef NS_ENUM(NSInteger, HomeViewMode) { HomeViewMode_Archive, HomeViewMode_Inbox, @@ -100,7 +102,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations return self; } -- (instancetype)initWithCoder:(NSCoder *)aDecoder +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { OWSFail(@"Do not load this from the storyboard."); @@ -227,6 +229,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations [self.tableView autoPinWidthToSuperview]; [self.tableView autoPinEdgeToSuperviewEdge:ALEdgeBottom]; [self.tableView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:missingContactsPermissionView]; + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.estimatedRowHeight = 60; UILabel *emptyBoxLabel = [UILabel new]; self.emptyBoxLabel = emptyBoxLabel; @@ -409,8 +413,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations [self presentViewController:navigationController animated:YES completion:nil]; } -- (UIViewController *)previewingContext:(id)previewingContext - viewControllerForLocation:(CGPoint)location +- (nullable UIViewController *)previewingContext:(id)previewingContext + viewControllerForLocation:(CGPoint)location { NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location]; @@ -804,11 +808,6 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations return thread; } -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - return HomeViewCell.rowHeight; -} - - (void)pullToRefreshPerformed:(UIRefreshControl *)refreshControl { OWSAssertIsOnMainThread(); @@ -828,7 +827,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations return; } -- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath +- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath { if ([self isIndexPathForArchivedConversations:indexPath]) { return @[]; @@ -1370,3 +1369,5 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/NewContactThreadViewController.m b/Signal/src/ViewControllers/NewContactThreadViewController.m index 886ba2ba8..ad070e939 100644 --- a/Signal/src/ViewControllers/NewContactThreadViewController.m +++ b/Signal/src/ViewControllers/NewContactThreadViewController.m @@ -124,6 +124,9 @@ NS_ASSUME_NONNULL_BEGIN [self.view addSubview:self.tableViewController.view]; [_tableViewController.view autoPinWidthToSuperview]; + self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableViewController.tableView.estimatedRowHeight = 60; + [_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:contactsPermissionReminderView]; [self autoPinViewToBottomOfViewControllerOrKeyboard:self.tableViewController.view]; _tableViewController.tableView.tableHeaderView = searchBar; diff --git a/SignalMessaging/Views/ContactTableViewCell.h b/SignalMessaging/Views/ContactTableViewCell.h index be11ee3ca..b62dcadaf 100644 --- a/SignalMessaging/Views/ContactTableViewCell.h +++ b/SignalMessaging/Views/ContactTableViewCell.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSContactsManager.h" @@ -12,7 +12,6 @@ NS_ASSUME_NONNULL_BEGIN -extern NSString *const kContactsTable_CellReuseIdentifier; extern const NSUInteger kContactTableViewCellAvatarSize; extern const CGFloat kContactTableViewCellAvatarTextMargin; @@ -25,7 +24,7 @@ extern const CGFloat kContactTableViewCellAvatarTextMargin; @property (nonatomic, nullable) NSString *accessoryMessage; @property (nonatomic, readonly) UILabel *subtitle; -+ (nullable NSString *)reuseIdentifier; ++ (NSString *)reuseIdentifier; + (CGFloat)rowHeight; diff --git a/SignalMessaging/Views/ContactTableViewCell.m b/SignalMessaging/Views/ContactTableViewCell.m index eadbc4cb2..d5dfd2700 100644 --- a/SignalMessaging/Views/ContactTableViewCell.m +++ b/SignalMessaging/Views/ContactTableViewCell.m @@ -18,7 +18,6 @@ NS_ASSUME_NONNULL_BEGIN -NSString *const kContactsTable_CellReuseIdentifier = @"kContactsTable_CellReuseIdentifier"; const NSUInteger kContactTableViewCellAvatarSize = 40; const CGFloat kContactTableViewCellAvatarTextMargin = 12; @@ -37,20 +36,15 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12; @implementation ContactTableViewCell -- (instancetype)init +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier { - if (self = [super init]) { + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { [self configureProgrammatically]; } return self; } -+ (nullable NSString *)reuseIdentifier -{ - return NSStringFromClass(self.class); -} - -- (nullable NSString *)reuseIdentifier ++ (NSString *)reuseIdentifier { return NSStringFromClass(self.class); } @@ -62,11 +56,15 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12; + (CGFloat)rowHeight { - return 59.f; + return 60.f; } - (void)configureProgrammatically { + OWSAssert(!self.nameLabel); + + const CGFloat kMinVMargin = 5; + self.preservesSuperviewLayoutMargins = YES; self.contentView.preservesSuperviewLayoutMargins = YES; @@ -110,6 +108,22 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12; [_nameContainerView autoPinLeadingToTrailingEdgeOfView:_avatarView offset:kContactTableViewCellAvatarTextMargin]; [_nameContainerView autoPinTrailingToSuperviewMargin]; + // Ensure that the cell's contents never overflow the cell bounds. + // We pin pin to the superview _edge_ and not _margin_ for the purposes + // of overflow, so that changes to the margins do not trip these safe guards. + [_avatarView autoPinEdgeToSuperviewEdge:ALEdgeTop + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; + [_avatarView autoPinEdgeToSuperviewEdge:ALEdgeBottom + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; + [_nameContainerView autoPinEdgeToSuperviewEdge:ALEdgeTop + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; + [_nameContainerView autoPinEdgeToSuperviewEdge:ALEdgeBottom + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; + [self configureFonts]; // Force layout, since imageView isn't being initally rendered on App Store optimized build. diff --git a/SignalMessaging/utils/ConversationSearcher.swift b/SignalMessaging/utils/ConversationSearcher.swift index e13156c72..2e87e0fa0 100644 --- a/SignalMessaging/utils/ConversationSearcher.swift +++ b/SignalMessaging/utils/ConversationSearcher.swift @@ -187,8 +187,6 @@ public class ConversationSearcher: NSObject { } } - // MARK: - Helpers - // MARK: Searchers private lazy var groupThreadSearcher: Searcher = Searcher { (groupThread: TSGroupThread) in @@ -202,11 +200,13 @@ public class ConversationSearcher: NSObject { private lazy var contactThreadSearcher: Searcher = Searcher { (contactThread: TSContactThread) in let recipientId = contactThread.contactIdentifier() + Logger.verbose("contactThreadSearcher: \(self.indexingString(recipientId: recipientId))") return self.indexingString(recipientId: recipientId) } private lazy var signalAccountSearcher: Searcher = Searcher { (signalAccount: SignalAccount) in let recipientId = signalAccount.recipientId + Logger.verbose("signalAccountSearcher: \(self.indexingString(recipientId: recipientId))") return self.indexingString(recipientId: recipientId) } From 3f9f2abcd8c4d691a929a6c4de54c3d06dd95686 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 12 Jun 2018 13:23:07 -0400 Subject: [PATCH 2/7] Style the search results. --- .../NewContactThreadViewController.m | 29 ++++++++++--------- .../ViewControllers/OWSTableViewController.m | 10 +++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/Signal/src/ViewControllers/NewContactThreadViewController.m b/Signal/src/ViewControllers/NewContactThreadViewController.m index ad070e939..ed296350d 100644 --- a/Signal/src/ViewControllers/NewContactThreadViewController.m +++ b/Signal/src/ViewControllers/NewContactThreadViewController.m @@ -432,19 +432,22 @@ NS_ASSUME_NONNULL_BEGIN NSArray *signalAccounts = collatedSignalAccounts[i]; NSMutableArray *contactItems = [NSMutableArray new]; for (SignalAccount *signalAccount in signalAccounts) { - [contactItems addObject:[OWSTableItem itemWithCustomCellBlock:^{ - ContactTableViewCell *cell = [ContactTableViewCell new]; - BOOL isBlocked = [self.contactsViewHelper isRecipientIdBlocked:signalAccount.recipientId]; - if (isBlocked) { - cell.accessoryMessage - = NSLocalizedString(@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked."); - } - - [cell configureWithSignalAccount:signalAccount contactsManager:self.contactsViewHelper.contactsManager]; - - return cell; - } - customRowHeight:[ContactTableViewCell rowHeight] + [contactItems addObject:[OWSTableItem + itemWithCustomCellBlock:^{ + ContactTableViewCell *cell = [ContactTableViewCell new]; + BOOL isBlocked = [self.contactsViewHelper + isRecipientIdBlocked:signalAccount.recipientId]; + if (isBlocked) { + cell.accessoryMessage = NSLocalizedString(@"CONTACT_CELL_IS_BLOCKED", + @"An indicator that a contact has been blocked."); + } + + [cell configureWithSignalAccount:signalAccount + contactsManager:self.contactsViewHelper.contactsManager]; + + return cell; + } + customRowHeight:UITableViewAutomaticDimension actionBlock:^{ [weakSelf newConversationWithRecipientId:signalAccount.recipientId]; }]]; diff --git a/SignalMessaging/ViewControllers/OWSTableViewController.m b/SignalMessaging/ViewControllers/OWSTableViewController.m index 187b9ff76..fe93caee5 100644 --- a/SignalMessaging/ViewControllers/OWSTableViewController.m +++ b/SignalMessaging/ViewControllers/OWSTableViewController.m @@ -112,7 +112,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f; actionBlock:(nullable OWSTableActionBlock)actionBlock { OWSAssert(customCell); - OWSAssert(customRowHeight > 0); + OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension); OWSTableItem *item = [OWSTableItem new]; item.actionBlock = actionBlock; @@ -125,7 +125,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f; customRowHeight:(CGFloat)customRowHeight actionBlock:(nullable OWSTableActionBlock)actionBlock { - OWSAssert(customRowHeight > 0); + OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension); OWSTableItem *item = [self itemWithCustomCellBlock:customCellBlock actionBlock:actionBlock]; item.customRowHeight = @(customRowHeight); @@ -177,7 +177,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f; customRowHeight:(CGFloat)customRowHeight actionBlock:(nullable OWSTableActionBlock)actionBlock { - OWSAssert(customRowHeight > 0); + OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension); OWSTableItem *item = [self disclosureItemWithText:text actionBlock:actionBlock]; item.customRowHeight = @(customRowHeight); @@ -237,7 +237,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f; customRowHeight:(CGFloat)customRowHeight actionBlock:(nullable OWSTableSubPageBlock)actionBlock { - OWSAssert(customRowHeight > 0); + OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension); OWSTableItem *item = [self subPageItemWithText:text actionBlock:actionBlock]; item.customRowHeight = @(customRowHeight); @@ -285,7 +285,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f; + (OWSTableItem *)softCenterLabelItemWithText:(NSString *)text customRowHeight:(CGFloat)customRowHeight { - OWSAssert(customRowHeight > 0); + OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension); OWSTableItem *item = [self softCenterLabelItemWithText:text]; item.customRowHeight = @(customRowHeight); From 9639d3cbae4443e096d512c28d0f4077dd2a272a Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 12 Jun 2018 13:34:30 -0400 Subject: [PATCH 3/7] Clean up ahead of PR. --- SignalMessaging/utils/ConversationSearcher.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/SignalMessaging/utils/ConversationSearcher.swift b/SignalMessaging/utils/ConversationSearcher.swift index 2e87e0fa0..90f7e8b31 100644 --- a/SignalMessaging/utils/ConversationSearcher.swift +++ b/SignalMessaging/utils/ConversationSearcher.swift @@ -200,13 +200,11 @@ public class ConversationSearcher: NSObject { private lazy var contactThreadSearcher: Searcher = Searcher { (contactThread: TSContactThread) in let recipientId = contactThread.contactIdentifier() - Logger.verbose("contactThreadSearcher: \(self.indexingString(recipientId: recipientId))") return self.indexingString(recipientId: recipientId) } private lazy var signalAccountSearcher: Searcher = Searcher { (signalAccount: SignalAccount) in let recipientId = signalAccount.recipientId - Logger.verbose("signalAccountSearcher: \(self.indexingString(recipientId: recipientId))") return self.indexingString(recipientId: recipientId) } From 31443b56879dcc3c62a9dba0d8c8fc0f0d2c2b20 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 12 Jun 2018 13:42:10 -0400 Subject: [PATCH 4/7] Clean up ahead of PR. --- Signal/src/ViewControllers/HomeView/HomeViewCell.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index 16c4dc067..67bb8382d 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -79,6 +79,13 @@ NS_ASSUME_NONNULL_BEGIN [self.avatarView autoVCenterInSuperview]; [self.avatarView setContentHuggingHigh]; [self.avatarView setCompressionResistanceHigh]; + const CGFloat kAvatarMinVMargin = 10; + [self.avatarView autoPinEdgeToSuperviewEdge:ALEdgeTop + withInset:kAvatarMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; + [self.avatarView autoPinEdgeToSuperviewEdge:ALEdgeBottom + withInset:kAvatarMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; self.payloadView = [UIStackView new]; self.payloadView.axis = UILayoutConstraintAxisVertical; From f0c1805de9996e324eb74aad7a0d51da9f5c8779 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 12 Jun 2018 17:18:24 -0400 Subject: [PATCH 5/7] Strip snippet formatting. --- .../ConversationSearchViewController.swift | 22 ++++++++----------- .../utils/ConversationSearcher.swift | 1 + 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift index 778367716..d9419bc4d 100644 --- a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift +++ b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift @@ -191,22 +191,18 @@ class ConversationSearchViewController: UITableViewController { } overrideTimestamp = NSNumber(value: message.timestamp) - guard let snippet = searchResult.snippet else { + guard let rawSnippet = searchResult.snippet else { owsFail("\(ConversationSearchViewController.logTag) message search result is missing snippet") return } - guard let snippetData = snippet.data(using: String.Encoding.utf8) else { - owsFail("\(ConversationSearchViewController.logTag) message search result has invalid snippet") - return - } - do { - let snippetOptions = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html] - overrideSnippet = try NSAttributedString(data: snippetData, - options: snippetOptions, - documentAttributes: nil) - } catch { - owsFail("\(ConversationSearchViewController.logTag) could not parse message snippet") - } + // YDB uses bold tags to highlight matches within the snippet. + let filteredSnippet = rawSnippet + .replacingOccurrences(of: "", with: "") + .replacingOccurrences(of: "", with: "") + .replacingOccurrences(of: "", with: "") + .replacingOccurrences(of: "", with: "") + + overrideSnippet = NSAttributedString(string: filteredSnippet) } cell.configure(withThread: searchResult.thread, diff --git a/SignalMessaging/utils/ConversationSearcher.swift b/SignalMessaging/utils/ConversationSearcher.swift index 90f7e8b31..965553ef3 100644 --- a/SignalMessaging/utils/ConversationSearcher.swift +++ b/SignalMessaging/utils/ConversationSearcher.swift @@ -106,6 +106,7 @@ public class ConversationSearcher: NSObject { var existingConversationRecipientIds: Set = Set() self.finder.enumerateObjects(searchText: searchText, transaction: transaction) { (match: Any, snippet: String?) in + if let thread = match as? TSThread { let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) let snippet: String? = thread.lastMessageText(transaction: transaction) From 99677899b18c412ad3ab42a8bb0a4eca7f7ba1e3 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 13 Jun 2018 09:54:18 -0400 Subject: [PATCH 6/7] Respond to CR. --- .../ConversationSearchViewController.swift | 53 ++++++++----------- .../utils/ConversationSearcher.swift | 16 ++++-- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift index d9419bc4d..9aa1a76d4 100644 --- a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift +++ b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift @@ -29,23 +29,15 @@ class ConversationSearchViewController: UITableViewController { case messages } - var contactsManager: OWSContactsManager { - return Environment.current().contactsManager - } - var blockedPhoneNumberSet = Set() // MARK: View Lifecyle - override public func loadView() { - super.loadView() + override func viewDidLoad() { + super.viewDidLoad() let blockingManager = OWSBlockingManager.shared() blockedPhoneNumberSet = Set(blockingManager.blockedPhoneNumbers()) - } - - override func viewDidLoad() { - super.viewDidLoad() tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = 60 @@ -179,30 +171,29 @@ class ConversationSearchViewController: UITableViewController { var overrideSnippet = NSAttributedString() var overrideTimestamp: NSNumber? - self.uiDatabaseConnection.read { transaction in - guard let messageId = searchResult.messageId else { - owsFail("\(ConversationSearchViewController.logTag) message search result is missing message id") - return - } - guard let message = TSInteraction.fetch(uniqueId: messageId, transaction: transaction) else { - // This could happen if the message has disappeared, etc. - Logger.error("\(ConversationSearchViewController.logTag) could not load message") - return + if let messageId = searchResult.messageId { + if let messageTimestamp = searchResult.messageTimestamp { + overrideTimestamp = NSNumber(value: messageTimestamp) + } else { + owsFail("\(ConversationSearchViewController.logTag) message search result is missing message timestamp") } - overrideTimestamp = NSNumber(value: message.timestamp) - guard let rawSnippet = searchResult.snippet else { - owsFail("\(ConversationSearchViewController.logTag) message search result is missing snippet") - return + // Note that we only use the snippet for message results, + // not conversation results. HomeViewCell will generate + // a snippet for conversations that reflects the latest + // contents. + if let messageSnippet = searchResult.snippet { + // YDB uses bold tags to highlight matches within the snippet. + let filteredSnippet = messageSnippet + .replacingOccurrences(of: "", with: "") + .replacingOccurrences(of: "", with: "") + .replacingOccurrences(of: "", with: "") + .replacingOccurrences(of: "", with: "") + + overrideSnippet = NSAttributedString(string: filteredSnippet) + } else { + owsFail("\(ConversationSearchViewController.logTag) message search result is missing message snippet") } - // YDB uses bold tags to highlight matches within the snippet. - let filteredSnippet = rawSnippet - .replacingOccurrences(of: "", with: "") - .replacingOccurrences(of: "", with: "") - .replacingOccurrences(of: "", with: "") - .replacingOccurrences(of: "", with: "") - - overrideSnippet = NSAttributedString(string: filteredSnippet) } cell.configure(withThread: searchResult.thread, diff --git a/SignalMessaging/utils/ConversationSearcher.swift b/SignalMessaging/utils/ConversationSearcher.swift index 965553ef3..9a4675662 100644 --- a/SignalMessaging/utils/ConversationSearcher.swift +++ b/SignalMessaging/utils/ConversationSearcher.swift @@ -9,16 +9,18 @@ public class ConversationSearchResult: Comparable { public let thread: ThreadViewModel public let messageId: String? + public let messageTimestamp: UInt64? public let snippet: String? private let sortKey: UInt64 - init(thread: ThreadViewModel, messageId: String?, snippet: String?, sortKey: UInt64) { + init(thread: ThreadViewModel, sortKey: UInt64, messageId: String? = nil, messageTimestamp: UInt64? = nil, snippet: String? = nil) { self.thread = thread + self.sortKey = sortKey self.messageId = messageId + self.messageTimestamp = messageTimestamp self.snippet = snippet - self.sortKey = sortKey } // Mark: Comparable @@ -109,9 +111,8 @@ public class ConversationSearcher: NSObject { if let thread = match as? TSThread { let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) - let snippet: String? = thread.lastMessageText(transaction: transaction) let sortKey = NSDate.ows_millisecondsSince1970(for: threadViewModel.lastMessageDate) - let searchResult = ConversationSearchResult(thread: threadViewModel, messageId: nil, snippet: snippet, sortKey: sortKey) + let searchResult = ConversationSearchResult(thread: threadViewModel, sortKey: sortKey) if let contactThread = thread as? TSContactThread { let recipientId = contactThread.contactIdentifier() @@ -124,7 +125,12 @@ public class ConversationSearcher: NSObject { let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) let sortKey = message.timestamp - let searchResult = ConversationSearchResult(thread: threadViewModel, messageId: message.uniqueId, snippet: snippet, sortKey: sortKey) + let searchResult = ConversationSearchResult(thread: threadViewModel, + sortKey: sortKey, + messageId: message.uniqueId, + messageTimestamp: message.timestamp, + snippet: snippet) + messages.append(searchResult) } else if let signalAccount = match as? SignalAccount { let searchResult = ContactSearchResult(signalAccount: signalAccount, contactsManager: contactsManager) From 44b23d44fd83e072d105b4f1ec9fb73dd5da5892 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 13 Jun 2018 10:23:12 -0400 Subject: [PATCH 7/7] Respond to CR. --- .../Cells/OWSContactShareView.m | 2 +- .../ConversationSearchViewController.swift | 21 +++++--------- .../ViewControllers/HomeView/HomeViewCell.h | 2 +- .../ViewControllers/HomeView/HomeViewCell.m | 29 +++++++------------ .../HomeView/HomeViewController.m | 2 +- SignalMessaging/Views/ContactTableViewCell.m | 2 +- .../utils/ConversationSearcher.swift | 8 ++--- .../src/Storage/FullTextSearchFinder.swift | 6 ++-- 8 files changed, 30 insertions(+), 42 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m index 1a60642fa..2e3323cec 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m @@ -235,7 +235,7 @@ NS_ASSUME_NONNULL_BEGIN [stackView autoPinTrailingToSuperviewMarginWithInset:self.iconHMargin]; [stackView autoVCenterInSuperview]; // Ensure that the cell's contents never overflow the cell bounds. - // We pin pin to the superview _edge_ and not _margin_ for the purposes + // We pin to the superview _edge_ and not _margin_ for the purposes // of overflow, so that changes to the margins do not trip these safe guards. [stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; diff --git a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift index 9aa1a76d4..f0ba2e854 100644 --- a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift +++ b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift @@ -170,10 +170,10 @@ class ConversationSearchViewController: UITableViewController { } var overrideSnippet = NSAttributedString() - var overrideTimestamp: NSNumber? - if let messageId = searchResult.messageId { - if let messageTimestamp = searchResult.messageTimestamp { - overrideTimestamp = NSNumber(value: messageTimestamp) + var overrideDate: Date? + if searchResult.messageId != nil { + if let messageDate = searchResult.messageDate { + overrideDate = messageDate } else { owsFail("\(ConversationSearchViewController.logTag) message search result is missing message timestamp") } @@ -183,14 +183,7 @@ class ConversationSearchViewController: UITableViewController { // a snippet for conversations that reflects the latest // contents. if let messageSnippet = searchResult.snippet { - // YDB uses bold tags to highlight matches within the snippet. - let filteredSnippet = messageSnippet - .replacingOccurrences(of: "", with: "") - .replacingOccurrences(of: "", with: "") - .replacingOccurrences(of: "", with: "") - .replacingOccurrences(of: "", with: "") - - overrideSnippet = NSAttributedString(string: filteredSnippet) + overrideSnippet = NSAttributedString(string: messageSnippet) } else { owsFail("\(ConversationSearchViewController.logTag) message search result is missing message snippet") } @@ -199,8 +192,8 @@ class ConversationSearchViewController: UITableViewController { cell.configure(withThread: searchResult.thread, contactsManager: contactsManager, blockedPhoneNumber: self.blockedPhoneNumberSet, - overrideSnippet: overrideSnippet, - overrideTimestamp: overrideTimestamp) + overrideSnippet: overrideSnippet, + overrideDate: overrideDate) return cell } diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.h b/Signal/src/ViewControllers/HomeView/HomeViewCell.h index c715ba40d..c7f249317 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.h +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.h @@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN contactsManager:(OWSContactsManager *)contactsManager blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet overrideSnippet:(nullable NSAttributedString *)overrideSnippet - overrideTimestamp:(nullable NSNumber *)overrideTimestamp; + overrideDate:(nullable NSDate *)overrideDate; @end diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index 67bb8382d..bef284b6c 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -93,10 +93,14 @@ NS_ASSUME_NONNULL_BEGIN [self.payloadView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:self.avatarHSpacing]; [self.payloadView autoVCenterInSuperview]; // Ensure that the cell's contents never overflow the cell bounds. - // We pin pin to the superview _edge_ and not _margin_ for the purposes + // We pin to the superview _edge_ and not _margin_ for the purposes // of overflow, so that changes to the margins do not trip these safe guards. - [self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; - [self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; + [self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeTop + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; + [self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeBottom + withInset:kMinVMargin + relation:NSLayoutRelationGreaterThanOrEqual]; // We pin the payloadView traillingEdge later, as part of the "Unread Badge" logic. self.nameLabel = [UILabel new]; @@ -136,16 +140,6 @@ NS_ASSUME_NONNULL_BEGIN [self.unreadLabel autoCenterInSuperview]; [self.unreadLabel setContentHuggingHigh]; [self.unreadLabel setCompressionResistanceHigh]; - - // Ensure that the cell's contents never overflow the cell bounds. - // We pin pin to the superview _edge_ and not _margin_ for the purposes - // of overflow, so that changes to the margins do not trip these safe guards. - [self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeTop - withInset:kMinVMargin - relation:NSLayoutRelationGreaterThanOrEqual]; - [self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeBottom - withInset:kMinVMargin - relation:NSLayoutRelationGreaterThanOrEqual]; } + (NSString *)cellReuseIdentifier @@ -171,14 +165,14 @@ NS_ASSUME_NONNULL_BEGIN contactsManager:contactsManager blockedPhoneNumberSet:blockedPhoneNumberSet overrideSnippet:nil - overrideTimestamp:nil]; + overrideDate:nil]; } - (void)configureWithThread:(ThreadViewModel *)thread contactsManager:(OWSContactsManager *)contactsManager blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet overrideSnippet:(nullable NSAttributedString *)overrideSnippet - overrideTimestamp:(nullable NSNumber *)overrideTimestamp + overrideDate:(nullable NSDate *)overrideDate { OWSAssertIsOnMainThread(); OWSAssert(thread); @@ -213,9 +207,8 @@ NS_ASSUME_NONNULL_BEGIN // override any font attributes. self.snippetLabel.font = [self snippetFont]; - self.dateTimeLabel.text = (overrideTimestamp - ? [self stringForDate:[NSDate ows_dateWithMillisecondsSince1970:overrideTimestamp.unsignedLongLongValue]] - : [self stringForDate:thread.lastMessageDate]); + self.dateTimeLabel.text + = (overrideDate ? [self stringForDate:overrideDate] : [self stringForDate:thread.lastMessageDate]); if (hasUnreadMessages) { self.dateTimeLabel.textColor = [UIColor ows_blackColor]; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index f4e7ec4aa..900a696f5 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -789,7 +789,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations [stackView autoPinEdgeToSuperviewMargin:ALEdgeLeading relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewMargin:ALEdgeTrailing relation:NSLayoutRelationGreaterThanOrEqual]; // Ensure that the cell's contents never overflow the cell bounds. - // We pin pin to the superview _edge_ and not _margin_ for the purposes + // We pin to the superview _edge_ and not _margin_ for the purposes // of overflow, so that changes to the margins do not trip these safe guards. [stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; diff --git a/SignalMessaging/Views/ContactTableViewCell.m b/SignalMessaging/Views/ContactTableViewCell.m index d5dfd2700..06abd423a 100644 --- a/SignalMessaging/Views/ContactTableViewCell.m +++ b/SignalMessaging/Views/ContactTableViewCell.m @@ -109,7 +109,7 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12; [_nameContainerView autoPinTrailingToSuperviewMargin]; // Ensure that the cell's contents never overflow the cell bounds. - // We pin pin to the superview _edge_ and not _margin_ for the purposes + // We pin to the superview _edge_ and not _margin_ for the purposes // of overflow, so that changes to the margins do not trip these safe guards. [_avatarView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:kMinVMargin diff --git a/SignalMessaging/utils/ConversationSearcher.swift b/SignalMessaging/utils/ConversationSearcher.swift index 9a4675662..ff72a4d55 100644 --- a/SignalMessaging/utils/ConversationSearcher.swift +++ b/SignalMessaging/utils/ConversationSearcher.swift @@ -9,17 +9,17 @@ public class ConversationSearchResult: Comparable { public let thread: ThreadViewModel public let messageId: String? - public let messageTimestamp: UInt64? + public let messageDate: Date? public let snippet: String? private let sortKey: UInt64 - init(thread: ThreadViewModel, sortKey: UInt64, messageId: String? = nil, messageTimestamp: UInt64? = nil, snippet: String? = nil) { + init(thread: ThreadViewModel, sortKey: UInt64, messageId: String? = nil, messageDate: Date? = nil, snippet: String? = nil) { self.thread = thread self.sortKey = sortKey self.messageId = messageId - self.messageTimestamp = messageTimestamp + self.messageDate = messageDate self.snippet = snippet } @@ -128,7 +128,7 @@ public class ConversationSearcher: NSObject { let searchResult = ConversationSearchResult(thread: threadViewModel, sortKey: sortKey, messageId: message.uniqueId, - messageTimestamp: message.timestamp, + messageDate: NSDate.ows_date(withMillisecondsSince1970: message.timestamp), snippet: snippet) messages.append(searchResult) diff --git a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift index d08e2941c..954c4b97d 100644 --- a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift +++ b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift @@ -50,8 +50,10 @@ public class FullTextSearchFinder: NSObject { let maxSearchResults = 500 var searchResultCount = 0 - // (snippet: String, collection: String, key: String, object: Any, stop: UnsafeMutablePointer) - ext.enumerateKeysAndObjects(matching: prefixQuery, with: nil) { (snippet: String, _: String, _: String, object: Any, stop: UnsafeMutablePointer) in + let snippetOptions = YapDatabaseFullTextSearchSnippetOptions() + snippetOptions.startMatchText = "" + snippetOptions.endMatchText = "" + ext.enumerateKeysAndObjects(matching: prefixQuery, with: snippetOptions) { (snippet: String, _: String, _: String, object: Any, stop: UnsafeMutablePointer) in guard searchResultCount < maxSearchResults else { stop.pointee = true return