mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'charlesmchen/openSearchResultMessage'
This commit is contained in:
commit
09138a1e04
11 changed files with 161 additions and 23 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
|||
// Don’t 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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue