Merge branch 'charlesmchen/openSearchResultMessage'

This commit is contained in:
Matthew Chen 2018-06-12 12:55:33 -04:00
commit 09138a1e04
11 changed files with 161 additions and 23 deletions

View file

@ -19,7 +19,9 @@ typedef NS_ENUM(NSUInteger, ConversationViewAction) {
@property (nonatomic, readonly) TSThread *thread;
- (void)configureForThread:(TSThread *)thread action:(ConversationViewAction)action;
- (void)configureForThread:(TSThread *)thread
action:(ConversationViewAction)action
focusMessageId:(nullable NSString *)focusMessageId;
- (void)popKeyBoard;

View file

@ -195,6 +195,8 @@ typedef enum : NSUInteger {
@property (nonatomic) NSUInteger lastRangeLength;
@property (nonatomic) ConversationViewAction actionOnOpen;
@property (nonatomic, nullable) NSString *focusMessageIdOnOpen;
@property (nonatomic) BOOL peek;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@ -426,11 +428,16 @@ typedef enum : NSUInteger {
[self hideInputIfNeeded];
}
- (void)configureForThread:(TSThread *)thread action:(ConversationViewAction)action
- (void)configureForThread:(TSThread *)thread
action:(ConversationViewAction)action
focusMessageId:(nullable NSString *)focusMessageId
{
OWSAssert(thread);
_thread = thread;
_isGroupConversation = [self.thread isKindOfClass:[TSGroupThread class]];
self.actionOnOpen = action;
self.focusMessageIdOnOpen = focusMessageId;
_cellMediaCache = [NSCache new];
// Cache the cell media for ~24 cells.
self.cellMediaCache.countLimit = 24;
@ -698,13 +705,43 @@ typedef enum : NSUInteger {
return nil;
}
- (NSIndexPath *_Nullable)indexPathOfMessageOnOpen
{
OWSAssert(self.focusMessageIdOnOpen);
OWSAssert(self.dynamicInteractions.focusMessagePosition);
if (!self.dynamicInteractions.focusMessagePosition) {
// This might happen if the focus message has disappeared
// before this view could appear.
OWSFail(@"%@ focus message has unknown position.", self.logTag);
return nil;
}
NSUInteger focusMessagePosition = self.dynamicInteractions.focusMessagePosition.unsignedIntegerValue;
if (focusMessagePosition >= self.viewItems.count) {
// This might happen if the focus message is outside the maximum
// valid load window size for this view.
OWSFail(@"%@ focus message has invalid position.", self.logTag);
return nil;
}
NSInteger row = (NSInteger)((self.viewItems.count - 1) - focusMessagePosition);
return [NSIndexPath indexPathForRow:row inSection:0];
}
- (void)scrollToDefaultPosition
{
if (self.isUserScrolling) {
return;
}
NSIndexPath *_Nullable indexPath = [self indexPathOfUnreadMessagesIndicator];
NSIndexPath *_Nullable indexPath = nil;
if (self.focusMessageIdOnOpen) {
indexPath = [self indexPathOfMessageOnOpen];
}
if (!indexPath) {
indexPath = [self indexPathOfUnreadMessagesIndicator];
}
if (indexPath) {
if (indexPath.section == 0 && indexPath.row == 0) {
[self.collectionView setContentOffset:CGPointZero animated:NO];
@ -1066,6 +1103,7 @@ typedef enum : NSUInteger {
[self startReadTimer];
[self updateNavigationBarSubtitleLabel];
[self updateBackButtonUnreadCount];
[self autoLoadMoreIfNecessary];
switch (self.actionOnOpen) {
case ConversationViewActionNone:
@ -1081,8 +1119,9 @@ typedef enum : NSUInteger {
break;
}
// Clear the "on open" state after the view has been presented.
self.actionOnOpen = ConversationViewActionNone;
self.focusMessageIdOnOpen = nil;
self.isViewCompletelyAppeared = YES;
self.viewHasEverAppeared = YES;
@ -1557,7 +1596,7 @@ typedef enum : NSUInteger {
// Dont auto-scroll after loading more messages unless we have more unseen messages.
//
// Otherwise, tapping on "load more messages" autoscrolls you downward which is completely wrong.
if (hasEarlierUnseenMessages) {
if (hasEarlierUnseenMessages && !self.focusMessageIdOnOpen) {
[self scrollToUnreadIndicatorAnimated];
}
}
@ -1634,9 +1673,20 @@ typedef enum : NSUInteger {
if (self.lastRangeLength == 0) {
// If this is the first time we're configuring the range length,
// try to take into account the position of the unread indicator.
// try to take into account the position of the unread indicator
// and the "focus message".
OWSAssert(self.dynamicInteractions);
if (self.focusMessageIdOnOpen) {
OWSAssert(self.dynamicInteractions.focusMessagePosition);
if (self.dynamicInteractions.focusMessagePosition) {
DDLogVerbose(@"%@ ensuring load of focus message: %@",
self.logTag,
self.dynamicInteractions.focusMessagePosition);
rangeLength = MAX(rangeLength, 1 + self.dynamicInteractions.focusMessagePosition.unsignedIntegerValue);
}
}
if (self.dynamicInteractions.unreadIndicatorPosition) {
NSUInteger unreadIndicatorPosition
= (NSUInteger)[self.dynamicInteractions.unreadIndicatorPosition longValue];
@ -1649,7 +1699,7 @@ typedef enum : NSUInteger {
// We'd like to include at least N seen messages,
// to give the user the context of where they left off the conversation.
const NSUInteger kPreferredSeenMessageCount = 1;
rangeLength = unreadIndicatorPosition + kPreferredSeenMessageCount;
rangeLength = MAX(rangeLength, unreadIndicatorPosition + kPreferredSeenMessageCount);
}
}
@ -2473,6 +2523,7 @@ typedef enum : NSUInteger {
dbConnection:self.editingDatabaseConnection
hideUnreadMessagesIndicator:self.hasClearedUnreadMessagesIndicator
firstUnseenInteractionTimestamp:self.dynamicInteractions.firstUnseenInteractionTimestamp
focusMessageId:self.focusMessageIdOnOpen
maxRangeSize:maxRangeSize];
}

View file

@ -79,7 +79,9 @@ class ConversationSearchViewController: UITableViewController {
}
let thread = searchResult.thread
SignalApp.shared().presentConversation(for: thread.threadRecord, action: .compose)
SignalApp.shared().presentConversation(for: thread.threadRecord,
action: .compose,
focusMessageId: searchResult.messageId)
}
}

View file

@ -11,6 +11,9 @@
@interface HomeViewController : OWSViewController
- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action;
- (void)presentThread:(TSThread *)thread
action:(ConversationViewAction)action
focusMessageId:(nullable NSString *)focusMessageId;
- (void)showNewConversationView;

View file

@ -424,7 +424,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
ConversationViewController *vc = [ConversationViewController new];
TSThread *thread = [self threadForIndexPath:indexPath];
self.lastThread = thread;
[vc configureForThread:thread action:ConversationViewActionNone];
[vc configureForThread:thread action:ConversationViewActionNone focusMessageId:nil];
[vc peekSetup];
return vc;
@ -1000,6 +1000,13 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
}
- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action
{
[self presentThread:thread action:action focusMessageId:nil];
}
- (void)presentThread:(TSThread *)thread
action:(ConversationViewAction)action
focusMessageId:(nullable NSString *)focusMessageId
{
if (thread == nil) {
OWSFail(@"Thread unexpectedly nil");
@ -1008,11 +1015,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
// We do this synchronously if we're already on the main thread.
DispatchMainThreadSafe(^{
ConversationViewController *mvc = [ConversationViewController new];
[mvc configureForThread:thread action:action];
ConversationViewController *viewController = [ConversationViewController new];
[viewController configureForThread:thread action:action focusMessageId:focusMessageId];
self.lastThread = thread;
[self pushTopLevelViewController:mvc animateDismissal:YES animatePresentation:YES];
[self pushTopLevelViewController:viewController animateDismissal:YES animatePresentation:YES];
});
}

View file

@ -45,10 +45,7 @@ import SignalMessaging
isVideo: Bool) -> Bool {
// Rather than an init-assigned dependency property, we access `callUIAdapter` via Environment
// because it can change after app launch due to user settings
guard let callUIAdapter = SignalApp.shared().callUIAdapter else {
owsFail("\(TAG) can't initiate call because callUIAdapter is nil")
return false
}
let callUIAdapter = SignalApp.shared().callUIAdapter
guard let frontmostViewController = UIApplication.shared.frontmostViewController else {
owsFail("\(TAG) could not identify frontmostViewController in \(#function)")
return false

View file

@ -4,6 +4,8 @@
#import "ConversationViewController.h"
NS_ASSUME_NONNULL_BEGIN
@class AccountManager;
@class CallService;
@class CallUIAdapter;
@ -17,8 +19,8 @@
@interface SignalApp : NSObject
@property (nonatomic, weak) HomeViewController *homeViewController;
@property (nonatomic, weak) OWSNavigationController *signUpFlowNavigationController;
@property (nonatomic, nullable, weak) HomeViewController *homeViewController;
@property (nonatomic, nullable, weak) OWSNavigationController *signUpFlowNavigationController;
// TODO: Convert to singletons?
@property (nonatomic, readonly) OWSWebRTCCallMessageHandler *callMessageHandler;
@ -40,6 +42,9 @@
- (void)presentConversationForThreadId:(NSString *)threadId;
- (void)presentConversationForThread:(TSThread *)thread;
- (void)presentConversationForThread:(TSThread *)thread action:(ConversationViewAction)action;
- (void)presentConversationForThread:(TSThread *)thread
action:(ConversationViewAction)action
focusMessageId:(nullable NSString *)focusMessageId;
#pragma mark - Methods
@ -48,3 +53,5 @@
+ (void)clearAllNotifications;
@end
NS_ASSUME_NONNULL_END

View file

@ -13,6 +13,8 @@
#import <SignalServiceKit/TSGroupThread.h>
#import <SignalServiceKit/Threading.h>
NS_ASSUME_NONNULL_BEGIN
@interface SignalApp ()
@property (nonatomic) OWSWebRTCCallMessageHandler *callMessageHandler;
@ -186,6 +188,13 @@
}
- (void)presentConversationForThread:(TSThread *)thread action:(ConversationViewAction)action
{
[self presentConversationForThread:thread action:action focusMessageId:nil];
}
- (void)presentConversationForThread:(TSThread *)thread
action:(ConversationViewAction)action
focusMessageId:(nullable NSString *)focusMessageId
{
OWSAssertIsOnMainThread();
@ -207,7 +216,7 @@
}
}
[self.homeViewController presentThread:thread action:action];
[self.homeViewController presentThread:thread action:action focusMessageId:focusMessageId];
});
}
@ -248,3 +257,5 @@
}
@end
NS_ASSUME_NONNULL_END

View file

@ -7,10 +7,14 @@ import SignalServiceKit
public class ConversationSearchResult {
public let thread: ThreadViewModel
public let messageId: String?
public let snippet: String?
init(thread: ThreadViewModel, snippet: String?) {
init(thread: ThreadViewModel, messageId: String?, snippet: String?) {
self.thread = thread
self.messageId = messageId
self.snippet = snippet
}
}
@ -71,7 +75,7 @@ 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 searchResult = ConversationSearchResult(thread: threadViewModel, snippet: snippet)
let searchResult = ConversationSearchResult(thread: threadViewModel, messageId: nil, snippet: snippet)
if let contactThread = thread as? TSContactThread {
let recipientId = contactThread.contactIdentifier()
@ -82,14 +86,14 @@ public class ConversationSearcher: NSObject {
let thread = message.thread(with: transaction)
let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
let searchResult = ConversationSearchResult(thread: threadViewModel, snippet: snippet)
let searchResult = ConversationSearchResult(thread: threadViewModel, messageId: message.uniqueId, snippet: snippet)
messages.append(searchResult)
} else if let signalAccount = match as? SignalAccount {
let searchResult = ContactSearchResult(signalAccount: signalAccount)
contacts.append(searchResult)
} else {
Logger.debug("\(self.logTag) in \(#function) unhandled item: \(match)")
owsFail("\(self.logTag) in \(#function) unhandled item: \(match)")
}
}

View file

@ -25,6 +25,15 @@ NS_ASSUME_NONNULL_BEGIN
// to include the unread indicator.
@property (nonatomic, nullable, readonly) NSNumber *unreadIndicatorPosition;
// Represents the "reverse index" of the focus message, if any.
// The "reverse index" is the distance of this interaction from
// the last interaction in the thread. Therefore the last interaction
// will have a "reverse index" of zero.
//
// We use "reverse indices" because (among other uses) we use this to
// determine the initial load window size.
@property (nonatomic, nullable, readonly) NSNumber *focusMessagePosition;
// If there are unseen messages in the thread, this is the timestamp
// of the oldest unseen message.
//
@ -105,6 +114,7 @@ NS_ASSUME_NONNULL_BEGIN
dbConnection:(YapDatabaseConnection *)dbConnection
hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator
firstUnseenInteractionTimestamp:(nullable NSNumber *)firstUnseenInteractionTimestamp
focusMessageId:(nullable NSString *)focusMessageId
maxRangeSize:(int)maxRangeSize;
+ (BOOL)shouldShowGroupProfileBannerInThread:(TSThread *)thread blockingManager:(OWSBlockingManager *)blockingManager;

View file

@ -31,6 +31,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, nullable) NSNumber *unreadIndicatorPosition;
@property (nonatomic, nullable) NSNumber *focusMessagePosition;
@property (nonatomic, nullable) NSNumber *firstUnseenInteractionTimestamp;
@property (nonatomic) BOOL hasMoreUnseenMessages;
@ -221,6 +223,7 @@ NS_ASSUME_NONNULL_BEGIN
hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator
firstUnseenInteractionTimestamp:
(nullable NSNumber *)firstUnseenInteractionTimestampParameter
focusMessageId:(nullable NSString *)focusMessageId
maxRangeSize:(int)maxRangeSize
{
OWSAssert(thread);
@ -615,11 +618,52 @@ NS_ASSUME_NONNULL_BEGIN
indicator.timestampForSorting);
}
}
// Determine the position of the focus message _after_ performing any mutations
// around dynamic interactions.
if (focusMessageId != nil) {
result.focusMessagePosition =
[self focusMessagePositionForThread:thread transaction:transaction focusMessageId:focusMessageId];
}
}];
return result;
}
+ (nullable NSNumber *)focusMessagePositionForThread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
focusMessageId:(NSString *)focusMessageId
{
OWSAssert(thread);
OWSAssert(transaction);
OWSAssert(focusMessageId);
YapDatabaseViewTransaction *databaseView = [transaction ext:TSMessageDatabaseViewExtensionName];
NSString *_Nullable group = nil;
NSUInteger index;
BOOL success =
[databaseView getGroup:&group index:&index forKey:focusMessageId inCollection:TSInteraction.collection];
if (!success) {
// This might happen if the focus message has disappeared
// before this view could appear.
OWSFail(@"%@ failed to find focus message index.", self.logTag);
return nil;
}
if (![group isEqualToString:thread.uniqueId]) {
OWSFail(@"%@ focus message has invalid group.", self.logTag);
return nil;
}
NSUInteger count = [databaseView numberOfItemsInGroup:thread.uniqueId];
if (index >= count) {
OWSFail(@"%@ focus message has invalid index.", self.logTag);
return nil;
}
NSUInteger position = (count - index) - 1;
return @(position);
}
+ (BOOL)shouldShowGroupProfileBannerInThread:(TSThread *)thread blockingManager:(OWSBlockingManager *)blockingManager
{
OWSAssert(thread);