Merge branch 'charlesmchen/styleSearchResults_'

pull/1/head
Matthew Chen 7 years ago
commit 8ebb10b900

@ -7,11 +7,13 @@
// Separate iOS Frameworks from other imports. // Separate iOS Frameworks from other imports.
#import "AppSettingsViewController.h" #import "AppSettingsViewController.h"
#import "ContactTableViewCell.h"
#import "ConversationViewItem.h" #import "ConversationViewItem.h"
#import "DateUtil.h" #import "DateUtil.h"
#import "DebugUIPage.h" #import "DebugUIPage.h"
#import "DebugUITableViewController.h" #import "DebugUITableViewController.h"
#import "FingerprintViewController.h" #import "FingerprintViewController.h"
#import "HomeViewCell.h"
#import "HomeViewController.h" #import "HomeViewController.h"
#import "MediaDetailViewController.h" #import "MediaDetailViewController.h"
#import "NotificationSettingsViewController.h" #import "NotificationSettingsViewController.h"

@ -235,7 +235,7 @@ NS_ASSUME_NONNULL_BEGIN
[stackView autoPinTrailingToSuperviewMarginWithInset:self.iconHMargin]; [stackView autoPinTrailingToSuperviewMarginWithInset:self.iconHMargin];
[stackView autoVCenterInSuperview]; [stackView autoVCenterInSuperview];
// Ensure that the cell's contents never overflow the cell bounds. // 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. // of overflow, so that changes to the margins do not trip these safe guards.
[stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual];
[stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual];

@ -29,18 +29,22 @@ class ConversationSearchViewController: UITableViewController {
case messages case messages
} }
var blockedPhoneNumberSet = Set<String>()
// MARK: View Lifecyle // MARK: View Lifecyle
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
let blockingManager = OWSBlockingManager.shared()
blockedPhoneNumberSet = Set(blockingManager.blockedPhoneNumbers())
tableView.rowHeight = UITableViewAutomaticDimension tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 60 tableView.estimatedRowHeight = 60
tableView.register(EmptySearchResultCell.self, forCellReuseIdentifier: EmptySearchResultCell.reuseIdentifier) tableView.register(EmptySearchResultCell.self, forCellReuseIdentifier: EmptySearchResultCell.reuseIdentifier)
tableView.register(ConversationSearchResultCell.self, forCellReuseIdentifier: ConversationSearchResultCell.reuseIdentifier) tableView.register(HomeViewCell.self, forCellReuseIdentifier: HomeViewCell.cellReuseIdentifier())
tableView.register(MessageSearchResultCell.self, forCellReuseIdentifier: MessageSearchResultCell.reuseIdentifier) tableView.register(ContactTableViewCell.self, forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier())
tableView.register(ContactSearchResultCell.self, forCellReuseIdentifier: ContactSearchResultCell.reuseIdentifier)
} }
// MARK: UITableViewDelegate // MARK: UITableViewDelegate
@ -131,7 +135,7 @@ class ConversationSearchViewController: UITableViewController {
cell.configure(searchText: searchText) cell.configure(searchText: searchText)
return cell return cell
case .conversations: 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") owsFail("cell was unexpectedly nil")
return UITableViewCell() return UITableViewCell()
} }
@ -140,10 +144,10 @@ class ConversationSearchViewController: UITableViewController {
owsFail("searchResult was unexpectedly nil") owsFail("searchResult was unexpectedly nil")
return UITableViewCell() return UITableViewCell()
} }
cell.configure(searchResult: searchResult) cell.configure(withThread: searchResult.thread, contactsManager: contactsManager, blockedPhoneNumber: self.blockedPhoneNumberSet)
return cell return cell
case .contacts: 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") owsFail("cell was unexpectedly nil")
return UITableViewCell() return UITableViewCell()
} }
@ -152,11 +156,10 @@ class ConversationSearchViewController: UITableViewController {
owsFail("searchResult was unexpectedly nil") owsFail("searchResult was unexpectedly nil")
return UITableViewCell() return UITableViewCell()
} }
cell.configure(with: searchResult.signalAccount, contactsManager: contactsManager)
cell.configure(searchResult: searchResult)
return cell return cell
case .messages: 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") owsFail("cell was unexpectedly nil")
return UITableViewCell() return UITableViewCell()
} }
@ -166,7 +169,32 @@ class ConversationSearchViewController: UITableViewController {
return UITableViewCell() return UITableViewCell()
} }
cell.configure(searchResult: searchResult) var overrideSnippet = NSAttributedString()
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")
}
// 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 {
overrideSnippet = NSAttributedString(string: messageSnippet)
} else {
owsFail("\(ConversationSearchViewController.logTag) message search result is missing message snippet")
}
}
cell.configure(withThread: searchResult.thread,
contactsManager: contactsManager,
blockedPhoneNumber: self.blockedPhoneNumberSet,
overrideSnippet: overrideSnippet,
overrideDate: overrideDate)
return cell return cell
} }
} }
@ -227,154 +255,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 <b> 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 { class EmptySearchResultCell: UITableViewCell {
static let reuseIdentifier = "EmptySearchResultCell" static let reuseIdentifier = "EmptySearchResultCell"

@ -10,14 +10,18 @@ NS_ASSUME_NONNULL_BEGIN
@interface HomeViewCell : UITableViewCell @interface HomeViewCell : UITableViewCell
+ (CGFloat)rowHeight;
+ (NSString *)cellReuseIdentifier; + (NSString *)cellReuseIdentifier;
- (void)configureWithThread:(ThreadViewModel *)thread - (void)configureWithThread:(ThreadViewModel *)thread
contactsManager:(OWSContactsManager *)contactsManager contactsManager:(OWSContactsManager *)contactsManager
blockedPhoneNumberSet:(NSSet<NSString *> *)blockedPhoneNumberSet; blockedPhoneNumberSet:(NSSet<NSString *> *)blockedPhoneNumberSet;
- (void)configureWithThread:(ThreadViewModel *)thread
contactsManager:(OWSContactsManager *)contactsManager
blockedPhoneNumberSet:(NSSet<NSString *> *)blockedPhoneNumberSet
overrideSnippet:(nullable NSAttributedString *)overrideSnippet
overrideDate:(nullable NSDate *)overrideDate;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -59,6 +59,8 @@ NS_ASSUME_NONNULL_BEGIN
{ {
OWSAssert(!self.avatarView); OWSAssert(!self.avatarView);
const CGFloat kMinVMargin = 5;
[self setTranslatesAutoresizingMaskIntoConstraints:NO]; [self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.layoutMargins = UIEdgeInsetsMake(0, self.cellHMargin, 0, self.cellHMargin); self.layoutMargins = UIEdgeInsetsMake(0, self.cellHMargin, 0, self.cellHMargin);
self.contentView.layoutMargins = UIEdgeInsetsZero; self.contentView.layoutMargins = UIEdgeInsetsZero;
@ -77,6 +79,13 @@ NS_ASSUME_NONNULL_BEGIN
[self.avatarView autoVCenterInSuperview]; [self.avatarView autoVCenterInSuperview];
[self.avatarView setContentHuggingHigh]; [self.avatarView setContentHuggingHigh];
[self.avatarView setCompressionResistanceHigh]; [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 = [UIStackView new];
self.payloadView.axis = UILayoutConstraintAxisVertical; self.payloadView.axis = UILayoutConstraintAxisVertical;
@ -84,10 +93,14 @@ NS_ASSUME_NONNULL_BEGIN
[self.payloadView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:self.avatarHSpacing]; [self.payloadView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:self.avatarHSpacing];
[self.payloadView autoVCenterInSuperview]; [self.payloadView autoVCenterInSuperview];
// Ensure that the cell's contents never overflow the cell bounds. // 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. // 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:ALEdgeTop
[self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; 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. // We pin the payloadView traillingEdge later, as part of the "Unread Badge" logic.
self.nameLabel = [UILabel new]; self.nameLabel = [UILabel new];
@ -147,6 +160,19 @@ NS_ASSUME_NONNULL_BEGIN
- (void)configureWithThread:(ThreadViewModel *)thread - (void)configureWithThread:(ThreadViewModel *)thread
contactsManager:(OWSContactsManager *)contactsManager contactsManager:(OWSContactsManager *)contactsManager
blockedPhoneNumberSet:(NSSet<NSString *> *)blockedPhoneNumberSet blockedPhoneNumberSet:(NSSet<NSString *> *)blockedPhoneNumberSet
{
[self configureWithThread:thread
contactsManager:contactsManager
blockedPhoneNumberSet:blockedPhoneNumberSet
overrideSnippet:nil
overrideDate:nil];
}
- (void)configureWithThread:(ThreadViewModel *)thread
contactsManager:(OWSContactsManager *)contactsManager
blockedPhoneNumberSet:(NSSet<NSString *> *)blockedPhoneNumberSet
overrideSnippet:(nullable NSAttributedString *)overrideSnippet
overrideDate:(nullable NSDate *)overrideDate
{ {
OWSAssertIsOnMainThread(); OWSAssertIsOnMainThread();
OWSAssert(thread); OWSAssert(thread);
@ -168,13 +194,21 @@ NS_ASSUME_NONNULL_BEGIN
self.payloadView.spacing = 0.f; self.payloadView.spacing = 0.f;
self.topRowView.spacing = self.topRowHSpacing; 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 // We update the fonts every time this cell is configured to ensure that
// changes to the dynamic type settings are reflected. // 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.font = [self snippetFont];
self.snippetLabel.attributedText =
[self attributedSnippetForThread:thread blockedPhoneNumberSet:blockedPhoneNumberSet];
self.dateTimeLabel.text = [self stringForDate:thread.lastMessageDate]; self.dateTimeLabel.text
= (overrideDate ? [self stringForDate:overrideDate] : [self stringForDate:thread.lastMessageDate]);
if (hasUnreadMessages) { if (hasUnreadMessages) {
self.dateTimeLabel.textColor = [UIColor ows_blackColor]; 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. // Spec check. Should be 12pts (6pt on each side) when using default font size.
OWSAssert(UIFont.ows_dynamicTypeBodyFont.pointSize != 17 || minMargin == 12); OWSAssert(UIFont.ows_dynamicTypeBodyFont.pointSize != 17 || minMargin == 12);
[self.viewConstraints addObject:[self.unreadBadge autoMatchDimension:ALDimensionWidth [self.viewConstraints addObjectsFromArray:@[
toDimension:ALDimensionWidth [self.unreadBadge autoMatchDimension:ALDimensionWidth
ofView:self.unreadLabel toDimension:ALDimensionWidth
withOffset:minMargin]]; 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:@[ [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 // Horizontally, badge is inserted after the tail of the payloadView, pushing back the date *and* snippet
// view // view
[self.payloadView autoPinEdge:ALEdgeTrailing [self.payloadView autoPinEdge:ALEdgeTrailing
@ -223,6 +260,12 @@ NS_ASSUME_NONNULL_BEGIN
ofView:self.unreadBadge ofView:self.unreadBadge
withOffset:-self.topRowHSpacing], withOffset:-self.topRowHSpacing],
[self.unreadBadge autoPinTrailingToSuperviewMargin], [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. // 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, // 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; 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 - (NSUInteger)cellHMargin
{ {
return 16; return 16;

@ -6,6 +6,8 @@
#import <SignalMessaging/OWSViewController.h> #import <SignalMessaging/OWSViewController.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class TSThread; @class TSThread;
@interface HomeViewController : OWSViewController @interface HomeViewController : OWSViewController
@ -25,3 +27,5 @@
animatePresentation:(BOOL)animatePresentation; animatePresentation:(BOOL)animatePresentation;
@end @end
NS_ASSUME_NONNULL_END

@ -31,6 +31,8 @@
#import <YapDatabase/YapDatabaseViewChange.h> #import <YapDatabase/YapDatabaseViewChange.h>
#import <YapDatabase/YapDatabaseViewConnection.h> #import <YapDatabase/YapDatabaseViewConnection.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, HomeViewMode) { typedef NS_ENUM(NSInteger, HomeViewMode) {
HomeViewMode_Archive, HomeViewMode_Archive,
HomeViewMode_Inbox, HomeViewMode_Inbox,
@ -100,7 +102,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
return self; return self;
} }
- (instancetype)initWithCoder:(NSCoder *)aDecoder - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{ {
OWSFail(@"Do not load this from the storyboard."); OWSFail(@"Do not load this from the storyboard.");
@ -227,6 +229,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
[self.tableView autoPinWidthToSuperview]; [self.tableView autoPinWidthToSuperview];
[self.tableView autoPinEdgeToSuperviewEdge:ALEdgeBottom]; [self.tableView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[self.tableView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:missingContactsPermissionView]; [self.tableView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:missingContactsPermissionView];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 60;
UILabel *emptyBoxLabel = [UILabel new]; UILabel *emptyBoxLabel = [UILabel new];
self.emptyBoxLabel = emptyBoxLabel; self.emptyBoxLabel = emptyBoxLabel;
@ -409,8 +413,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
[self presentViewController:navigationController animated:YES completion:nil]; [self presentViewController:navigationController animated:YES completion:nil];
} }
- (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext - (nullable UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext
viewControllerForLocation:(CGPoint)location viewControllerForLocation:(CGPoint)location
{ {
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location]; NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
@ -785,7 +789,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
[stackView autoPinEdgeToSuperviewMargin:ALEdgeLeading relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewMargin:ALEdgeLeading relation:NSLayoutRelationGreaterThanOrEqual];
[stackView autoPinEdgeToSuperviewMargin:ALEdgeTrailing relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewMargin:ALEdgeTrailing relation:NSLayoutRelationGreaterThanOrEqual];
// Ensure that the cell's contents never overflow the cell bounds. // 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. // of overflow, so that changes to the margins do not trip these safe guards.
[stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual];
[stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual];
@ -804,11 +808,6 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
return thread; return thread;
} }
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return HomeViewCell.rowHeight;
}
- (void)pullToRefreshPerformed:(UIRefreshControl *)refreshControl - (void)pullToRefreshPerformed:(UIRefreshControl *)refreshControl
{ {
OWSAssertIsOnMainThread(); OWSAssertIsOnMainThread();
@ -828,7 +827,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
return; return;
} }
- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath - (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{ {
if ([self isIndexPathForArchivedConversations:indexPath]) { if ([self isIndexPathForArchivedConversations:indexPath]) {
return @[]; return @[];
@ -1370,3 +1369,5 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
} }
@end @end
NS_ASSUME_NONNULL_END

@ -124,6 +124,9 @@ NS_ASSUME_NONNULL_BEGIN
[self.view addSubview:self.tableViewController.view]; [self.view addSubview:self.tableViewController.view];
[_tableViewController.view autoPinWidthToSuperview]; [_tableViewController.view autoPinWidthToSuperview];
self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableViewController.tableView.estimatedRowHeight = 60;
[_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:contactsPermissionReminderView]; [_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:contactsPermissionReminderView];
[self autoPinViewToBottomOfViewControllerOrKeyboard:self.tableViewController.view]; [self autoPinViewToBottomOfViewControllerOrKeyboard:self.tableViewController.view];
_tableViewController.tableView.tableHeaderView = searchBar; _tableViewController.tableView.tableHeaderView = searchBar;
@ -429,19 +432,22 @@ NS_ASSUME_NONNULL_BEGIN
NSArray<SignalAccount *> *signalAccounts = collatedSignalAccounts[i]; NSArray<SignalAccount *> *signalAccounts = collatedSignalAccounts[i];
NSMutableArray <OWSTableItem *> *contactItems = [NSMutableArray new]; NSMutableArray <OWSTableItem *> *contactItems = [NSMutableArray new];
for (SignalAccount *signalAccount in signalAccounts) { for (SignalAccount *signalAccount in signalAccounts) {
[contactItems addObject:[OWSTableItem itemWithCustomCellBlock:^{ [contactItems addObject:[OWSTableItem
ContactTableViewCell *cell = [ContactTableViewCell new]; itemWithCustomCellBlock:^{
BOOL isBlocked = [self.contactsViewHelper isRecipientIdBlocked:signalAccount.recipientId]; ContactTableViewCell *cell = [ContactTableViewCell new];
if (isBlocked) { BOOL isBlocked = [self.contactsViewHelper
cell.accessoryMessage isRecipientIdBlocked:signalAccount.recipientId];
= NSLocalizedString(@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked."); 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; [cell configureWithSignalAccount:signalAccount
} contactsManager:self.contactsViewHelper.contactsManager];
customRowHeight:[ContactTableViewCell rowHeight]
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{ actionBlock:^{
[weakSelf newConversationWithRecipientId:signalAccount.recipientId]; [weakSelf newConversationWithRecipientId:signalAccount.recipientId];
}]]; }]];

@ -112,7 +112,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
actionBlock:(nullable OWSTableActionBlock)actionBlock actionBlock:(nullable OWSTableActionBlock)actionBlock
{ {
OWSAssert(customCell); OWSAssert(customCell);
OWSAssert(customRowHeight > 0); OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension);
OWSTableItem *item = [OWSTableItem new]; OWSTableItem *item = [OWSTableItem new];
item.actionBlock = actionBlock; item.actionBlock = actionBlock;
@ -125,7 +125,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
customRowHeight:(CGFloat)customRowHeight customRowHeight:(CGFloat)customRowHeight
actionBlock:(nullable OWSTableActionBlock)actionBlock actionBlock:(nullable OWSTableActionBlock)actionBlock
{ {
OWSAssert(customRowHeight > 0); OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension);
OWSTableItem *item = [self itemWithCustomCellBlock:customCellBlock actionBlock:actionBlock]; OWSTableItem *item = [self itemWithCustomCellBlock:customCellBlock actionBlock:actionBlock];
item.customRowHeight = @(customRowHeight); item.customRowHeight = @(customRowHeight);
@ -177,7 +177,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
customRowHeight:(CGFloat)customRowHeight customRowHeight:(CGFloat)customRowHeight
actionBlock:(nullable OWSTableActionBlock)actionBlock actionBlock:(nullable OWSTableActionBlock)actionBlock
{ {
OWSAssert(customRowHeight > 0); OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension);
OWSTableItem *item = [self disclosureItemWithText:text actionBlock:actionBlock]; OWSTableItem *item = [self disclosureItemWithText:text actionBlock:actionBlock];
item.customRowHeight = @(customRowHeight); item.customRowHeight = @(customRowHeight);
@ -237,7 +237,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
customRowHeight:(CGFloat)customRowHeight customRowHeight:(CGFloat)customRowHeight
actionBlock:(nullable OWSTableSubPageBlock)actionBlock actionBlock:(nullable OWSTableSubPageBlock)actionBlock
{ {
OWSAssert(customRowHeight > 0); OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension);
OWSTableItem *item = [self subPageItemWithText:text actionBlock:actionBlock]; OWSTableItem *item = [self subPageItemWithText:text actionBlock:actionBlock];
item.customRowHeight = @(customRowHeight); item.customRowHeight = @(customRowHeight);
@ -285,7 +285,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
+ (OWSTableItem *)softCenterLabelItemWithText:(NSString *)text customRowHeight:(CGFloat)customRowHeight + (OWSTableItem *)softCenterLabelItemWithText:(NSString *)text customRowHeight:(CGFloat)customRowHeight
{ {
OWSAssert(customRowHeight > 0); OWSAssert(customRowHeight > 0 || customRowHeight == UITableViewAutomaticDimension);
OWSTableItem *item = [self softCenterLabelItemWithText:text]; OWSTableItem *item = [self softCenterLabelItemWithText:text];
item.customRowHeight = @(customRowHeight); item.customRowHeight = @(customRowHeight);

@ -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" #import "OWSContactsManager.h"
@ -12,7 +12,6 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
extern NSString *const kContactsTable_CellReuseIdentifier;
extern const NSUInteger kContactTableViewCellAvatarSize; extern const NSUInteger kContactTableViewCellAvatarSize;
extern const CGFloat kContactTableViewCellAvatarTextMargin; extern const CGFloat kContactTableViewCellAvatarTextMargin;
@ -25,7 +24,7 @@ extern const CGFloat kContactTableViewCellAvatarTextMargin;
@property (nonatomic, nullable) NSString *accessoryMessage; @property (nonatomic, nullable) NSString *accessoryMessage;
@property (nonatomic, readonly) UILabel *subtitle; @property (nonatomic, readonly) UILabel *subtitle;
+ (nullable NSString *)reuseIdentifier; + (NSString *)reuseIdentifier;
+ (CGFloat)rowHeight; + (CGFloat)rowHeight;

@ -18,7 +18,6 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
NSString *const kContactsTable_CellReuseIdentifier = @"kContactsTable_CellReuseIdentifier";
const NSUInteger kContactTableViewCellAvatarSize = 40; const NSUInteger kContactTableViewCellAvatarSize = 40;
const CGFloat kContactTableViewCellAvatarTextMargin = 12; const CGFloat kContactTableViewCellAvatarTextMargin = 12;
@ -37,20 +36,15 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12;
@implementation ContactTableViewCell @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]; [self configureProgrammatically];
} }
return self; return self;
} }
+ (nullable NSString *)reuseIdentifier + (NSString *)reuseIdentifier
{
return NSStringFromClass(self.class);
}
- (nullable NSString *)reuseIdentifier
{ {
return NSStringFromClass(self.class); return NSStringFromClass(self.class);
} }
@ -62,11 +56,15 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12;
+ (CGFloat)rowHeight + (CGFloat)rowHeight
{ {
return 59.f; return 60.f;
} }
- (void)configureProgrammatically - (void)configureProgrammatically
{ {
OWSAssert(!self.nameLabel);
const CGFloat kMinVMargin = 5;
self.preservesSuperviewLayoutMargins = YES; self.preservesSuperviewLayoutMargins = YES;
self.contentView.preservesSuperviewLayoutMargins = YES; self.contentView.preservesSuperviewLayoutMargins = YES;
@ -110,6 +108,22 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12;
[_nameContainerView autoPinLeadingToTrailingEdgeOfView:_avatarView offset:kContactTableViewCellAvatarTextMargin]; [_nameContainerView autoPinLeadingToTrailingEdgeOfView:_avatarView offset:kContactTableViewCellAvatarTextMargin];
[_nameContainerView autoPinTrailingToSuperviewMargin]; [_nameContainerView autoPinTrailingToSuperviewMargin];
// Ensure that the cell's contents never overflow the cell bounds.
// 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
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]; [self configureFonts];
// Force layout, since imageView isn't being initally rendered on App Store optimized build. // Force layout, since imageView isn't being initally rendered on App Store optimized build.

@ -9,16 +9,18 @@ public class ConversationSearchResult: Comparable {
public let thread: ThreadViewModel public let thread: ThreadViewModel
public let messageId: String? public let messageId: String?
public let messageDate: Date?
public let snippet: String? public let snippet: String?
private let sortKey: UInt64 private let sortKey: UInt64
init(thread: ThreadViewModel, messageId: String?, snippet: String?, sortKey: UInt64) { init(thread: ThreadViewModel, sortKey: UInt64, messageId: String? = nil, messageDate: Date? = nil, snippet: String? = nil) {
self.thread = thread self.thread = thread
self.sortKey = sortKey
self.messageId = messageId self.messageId = messageId
self.messageDate = messageDate
self.snippet = snippet self.snippet = snippet
self.sortKey = sortKey
} }
// Mark: Comparable // Mark: Comparable
@ -106,11 +108,11 @@ public class ConversationSearcher: NSObject {
var existingConversationRecipientIds: Set<String> = Set() var existingConversationRecipientIds: Set<String> = Set()
self.finder.enumerateObjects(searchText: searchText, transaction: transaction) { (match: Any, snippet: String?) in self.finder.enumerateObjects(searchText: searchText, transaction: transaction) { (match: Any, snippet: String?) in
if let thread = match as? TSThread { if let thread = match as? TSThread {
let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
let snippet: String? = thread.lastMessageText(transaction: transaction)
let sortKey = NSDate.ows_millisecondsSince1970(for: threadViewModel.lastMessageDate) 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 { if let contactThread = thread as? TSContactThread {
let recipientId = contactThread.contactIdentifier() let recipientId = contactThread.contactIdentifier()
@ -123,7 +125,12 @@ public class ConversationSearcher: NSObject {
let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
let sortKey = message.timestamp 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,
messageDate: NSDate.ows_date(withMillisecondsSince1970: message.timestamp),
snippet: snippet)
messages.append(searchResult) messages.append(searchResult)
} else if let signalAccount = match as? SignalAccount { } else if let signalAccount = match as? SignalAccount {
let searchResult = ContactSearchResult(signalAccount: signalAccount, contactsManager: contactsManager) let searchResult = ContactSearchResult(signalAccount: signalAccount, contactsManager: contactsManager)
@ -187,8 +194,6 @@ public class ConversationSearcher: NSObject {
} }
} }
// MARK: - Helpers
// MARK: Searchers // MARK: Searchers
private lazy var groupThreadSearcher: Searcher<TSGroupThread> = Searcher { (groupThread: TSGroupThread) in private lazy var groupThreadSearcher: Searcher<TSGroupThread> = Searcher { (groupThread: TSGroupThread) in

@ -50,8 +50,10 @@ public class FullTextSearchFinder: NSObject {
let maxSearchResults = 500 let maxSearchResults = 500
var searchResultCount = 0 var searchResultCount = 0
// (snippet: String, collection: String, key: String, object: Any, stop: UnsafeMutablePointer<ObjCBool>) let snippetOptions = YapDatabaseFullTextSearchSnippetOptions()
ext.enumerateKeysAndObjects(matching: prefixQuery, with: nil) { (snippet: String, _: String, _: String, object: Any, stop: UnsafeMutablePointer<ObjCBool>) in snippetOptions.startMatchText = ""
snippetOptions.endMatchText = ""
ext.enumerateKeysAndObjects(matching: prefixQuery, with: snippetOptions) { (snippet: String, _: String, _: String, object: Any, stop: UnsafeMutablePointer<ObjCBool>) in
guard searchResultCount < maxSearchResults else { guard searchResultCount < maxSearchResults else {
stop.pointee = true stop.pointee = true
return return

Loading…
Cancel
Save