mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Respond to CR.
This commit is contained in:
parent
a8e9b87f03
commit
6ab8ea9b6e
5 changed files with 118 additions and 80 deletions
|
@ -9,7 +9,7 @@
|
|||
<key>OSXVersion</key>
|
||||
<string>10.14.3</string>
|
||||
<key>WebRTCCommit</key>
|
||||
<string>55de5593cc261fa9368c5ccde98884ed1e278ba0 M72</string>
|
||||
<string>1445d719bf05280270e9f77576f80f973fd847f8 M73</string>
|
||||
</dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
|
|
|
@ -212,7 +212,7 @@ typedef enum : NSUInteger {
|
|||
@property (nonatomic, nullable) NSString *lastSearchedText;
|
||||
@property (nonatomic) BOOL isShowingSearchUI;
|
||||
@property (nonatomic, nullable) MenuActionsViewController *menuActionsViewController;
|
||||
@property (nonatomic) CGFloat contentInsetPadding;
|
||||
@property (nonatomic) CGFloat extraContentInsetPadding;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -749,7 +749,7 @@ typedef enum : NSUInteger {
|
|||
if (!self.viewHasEverAppeared) {
|
||||
[self scrollToDefaultPosition];
|
||||
} else if (self.menuActionsViewController != nil) {
|
||||
[self scrollToFocusInteraction:NO];
|
||||
[self scrollToMenuActionInteraction:NO];
|
||||
}
|
||||
|
||||
[self updateLastVisibleSortId];
|
||||
|
@ -763,7 +763,7 @@ typedef enum : NSUInteger {
|
|||
|
||||
- (NSArray<id<ConversationViewItem>> *)viewItems
|
||||
{
|
||||
return self.conversationViewModel.viewItems;
|
||||
return self.conversationViewModel.viewState.viewItems;
|
||||
}
|
||||
|
||||
- (ThreadDynamicInteractions *)dynamicInteractions
|
||||
|
@ -773,15 +773,12 @@ typedef enum : NSUInteger {
|
|||
|
||||
- (NSIndexPath *_Nullable)indexPathOfUnreadMessagesIndicator
|
||||
{
|
||||
NSInteger row = 0;
|
||||
for (id<ConversationViewItem> viewItem in self.viewItems) {
|
||||
if (viewItem.unreadIndicator) {
|
||||
return [NSIndexPath indexPathForRow:row inSection:0];
|
||||
}
|
||||
row++;
|
||||
}
|
||||
NSNumber *_Nullable unreadIndicatorIndex = self.conversationViewModel.viewState.unreadIndicatorIndex;
|
||||
if (unreadIndicatorIndex == nil) {
|
||||
return nil;
|
||||
}
|
||||
return [NSIndexPath indexPathForRow:unreadIndicatorIndex.integerValue inSection:0];
|
||||
}
|
||||
|
||||
- (NSIndexPath *_Nullable)indexPathOfMessageOnOpen
|
||||
{
|
||||
|
@ -1981,11 +1978,11 @@ typedef enum : NSUInteger {
|
|||
// which we might want to scroll to the bottom of the screen to
|
||||
// pin above the menu actions popup.
|
||||
CGSize mainScreenSize = UIScreen.mainScreen.bounds.size;
|
||||
self.contentInsetPadding = MAX(mainScreenSize.width, mainScreenSize.height);
|
||||
self.extraContentInsetPadding = MAX(mainScreenSize.width, mainScreenSize.height);
|
||||
|
||||
UIEdgeInsets contentInset = self.collectionView.contentInset;
|
||||
contentInset.top += self.contentInsetPadding;
|
||||
contentInset.bottom += self.contentInsetPadding;
|
||||
contentInset.top += self.extraContentInsetPadding;
|
||||
contentInset.bottom += self.extraContentInsetPadding;
|
||||
self.collectionView.contentInset = contentInset;
|
||||
|
||||
self.menuActionsViewController = menuActionsViewController;
|
||||
|
@ -1996,14 +1993,14 @@ typedef enum : NSUInteger {
|
|||
OWSLogVerbose(@"");
|
||||
|
||||
// Changes made in this "is presenting" callback are animated by the caller.
|
||||
[self scrollToFocusInteraction:NO];
|
||||
[self scrollToMenuActionInteraction:NO];
|
||||
}
|
||||
|
||||
- (void)menuActionsDidPresent:(MenuActionsViewController *)menuActionsViewController
|
||||
{
|
||||
OWSLogVerbose(@"");
|
||||
|
||||
[self scrollToFocusInteraction:NO];
|
||||
[self scrollToMenuActionInteraction:NO];
|
||||
}
|
||||
|
||||
- (void)menuActionsIsDismissing:(MenuActionsViewController *)menuActionsViewController
|
||||
|
@ -2038,24 +2035,26 @@ typedef enum : NSUInteger {
|
|||
}
|
||||
|
||||
UIEdgeInsets contentInset = self.collectionView.contentInset;
|
||||
contentInset.top -= self.contentInsetPadding;
|
||||
contentInset.bottom -= self.contentInsetPadding;
|
||||
contentInset.top -= self.extraContentInsetPadding;
|
||||
contentInset.bottom -= self.extraContentInsetPadding;
|
||||
self.collectionView.contentInset = contentInset;
|
||||
|
||||
self.menuActionsViewController = nil;
|
||||
self.contentInsetPadding = 0;
|
||||
self.extraContentInsetPadding = 0;
|
||||
}
|
||||
|
||||
- (void)scrollToFocusInteractionIfNecessary
|
||||
- (void)scrollToMenuActionInteractionIfNecessary
|
||||
{
|
||||
if (self.menuActionsViewController != nil) {
|
||||
[self scrollToFocusInteraction:NO];
|
||||
[self scrollToMenuActionInteraction:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollToFocusInteraction:(BOOL)animated
|
||||
- (void)scrollToMenuActionInteraction:(BOOL)animated
|
||||
{
|
||||
NSValue *_Nullable contentOffset = [self contentOffsetForFocusInteraction];
|
||||
OWSAssertDebug(self.menuActionsViewController);
|
||||
|
||||
NSValue *_Nullable contentOffset = [self contentOffsetForMenuActionInteraction];
|
||||
if (contentOffset == nil) {
|
||||
OWSFailDebug(@"Missing contentOffset.");
|
||||
return;
|
||||
|
@ -2063,11 +2062,13 @@ typedef enum : NSUInteger {
|
|||
[self.collectionView setContentOffset:contentOffset.CGPointValue animated:animated];
|
||||
}
|
||||
|
||||
- (nullable NSValue *)contentOffsetForFocusInteraction
|
||||
- (nullable NSValue *)contentOffsetForMenuActionInteraction
|
||||
{
|
||||
NSString *_Nullable focusedInteractionId = self.menuActionsViewController.focusedInteraction.uniqueId;
|
||||
if (focusedInteractionId == nil) {
|
||||
// This is expected if there is no focus interaction.
|
||||
OWSAssertDebug(self.menuActionsViewController);
|
||||
|
||||
NSString *_Nullable menuActionInteractionId = self.menuActionsViewController.focusedInteraction.uniqueId;
|
||||
if (menuActionInteractionId == nil) {
|
||||
OWSFailDebug(@"Missing menu action interaction.");
|
||||
return nil;
|
||||
}
|
||||
CGPoint modalTopWindow = [self.menuActionsViewController.focusUI convertPoint:CGPointZero toView:nil];
|
||||
|
@ -2075,18 +2076,13 @@ typedef enum : NSUInteger {
|
|||
CGPoint offset = modalTopLocal;
|
||||
CGFloat focusTop = offset.y - self.menuActionsViewController.vSpacing;
|
||||
|
||||
NSIndexPath *_Nullable indexPath = nil;
|
||||
for (NSUInteger i = 0; i < self.viewItems.count; i++) {
|
||||
id<ConversationViewItem> viewItem = self.viewItems[i];
|
||||
if ([viewItem.interaction.uniqueId isEqualToString:focusedInteractionId]) {
|
||||
indexPath = [NSIndexPath indexPathForRow:(NSInteger)i inSection:0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (indexPath == nil) {
|
||||
// This is expected if the focus interaction is being deleted.
|
||||
NSNumber *_Nullable interactionIndex
|
||||
= self.conversationViewModel.viewState.interactionIndexMap[menuActionInteractionId];
|
||||
if (interactionIndex == nil) {
|
||||
// This is expected if the menu action interaction is being deleted.
|
||||
return nil;
|
||||
}
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:interactionIndex.integerValue inSection:0];
|
||||
UICollectionViewLayoutAttributes *_Nullable layoutAttributes =
|
||||
[self.layout layoutAttributesForItemAtIndexPath:indexPath];
|
||||
if (layoutAttributes == nil) {
|
||||
|
@ -2109,16 +2105,12 @@ typedef enum : NSUInteger {
|
|||
if (!OWSWindowManager.sharedManager.isPresentingMenuActions) {
|
||||
return NO;
|
||||
}
|
||||
NSString *_Nullable focusedInteractionId = self.menuActionsViewController.focusedInteraction.uniqueId;
|
||||
if (focusedInteractionId == nil) {
|
||||
NSString *_Nullable menuActionInteractionId = self.menuActionsViewController.focusedInteraction.uniqueId;
|
||||
if (menuActionInteractionId == nil) {
|
||||
return NO;
|
||||
}
|
||||
for (id<ConversationViewItem> viewItem in self.viewItems) {
|
||||
if ([viewItem.interaction.uniqueId isEqualToString:focusedInteractionId]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
// Check whether there is still a view item for this interaction.
|
||||
return (self.conversationViewModel.viewState.interactionIndexMap[menuActionInteractionId] == nil);
|
||||
}
|
||||
|
||||
#pragma mark - ConversationViewCellDelegate
|
||||
|
@ -3857,8 +3849,8 @@ typedef enum : NSUInteger {
|
|||
newInsets.top = 0;
|
||||
newInsets.bottom = MAX(0, self.view.height - self.bottomLayoutGuide.length - keyboardEndFrameConverted.origin.y);
|
||||
|
||||
newInsets.top += self.contentInsetPadding;
|
||||
newInsets.bottom += self.contentInsetPadding;
|
||||
newInsets.top += self.extraContentInsetPadding;
|
||||
newInsets.bottom += self.extraContentInsetPadding;
|
||||
|
||||
BOOL wasScrolledToBottom = [self isScrolledToBottom];
|
||||
|
||||
|
@ -4515,7 +4507,7 @@ typedef enum : NSUInteger {
|
|||
targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
|
||||
{
|
||||
if (self.menuActionsViewController != nil) {
|
||||
NSValue *_Nullable contentOffset = [self contentOffsetForFocusInteraction];
|
||||
NSValue *_Nullable contentOffset = [self contentOffsetForMenuActionInteraction];
|
||||
if (contentOffset != nil) {
|
||||
return contentOffset.CGPointValue;
|
||||
}
|
||||
|
@ -4768,6 +4760,11 @@ typedef enum : NSUInteger {
|
|||
OWSAssertDebug(conversationUpdate);
|
||||
OWSAssertDebug(self.conversationViewModel);
|
||||
|
||||
if (!self.viewLoaded) {
|
||||
OWSLogVerbose(@"Ignoring update; view has not yet loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateBackButtonUnreadCount];
|
||||
[self updateNavigationBarSubtitleLabel];
|
||||
[self dismissMenuActionsIfNecessary];
|
||||
|
@ -5018,7 +5015,7 @@ typedef enum : NSUInteger {
|
|||
[strongSelf updateInputToolbarLayout];
|
||||
|
||||
if (self.menuActionsViewController != nil) {
|
||||
[self scrollToFocusInteraction:NO];
|
||||
[self scrollToMenuActionInteraction:NO];
|
||||
} else if (lastVisibleIndexPath) {
|
||||
[strongSelf.collectionView scrollToItemAtIndexPath:lastVisibleIndexPath
|
||||
atScrollPosition:UICollectionViewScrollPositionBottom
|
||||
|
|
|
@ -34,6 +34,19 @@ typedef NS_ENUM(NSUInteger, ConversationUpdateItemType) {
|
|||
|
||||
#pragma mark -
|
||||
|
||||
@interface ConversationViewState : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSArray<id<ConversationViewItem>> *viewItems;
|
||||
@property (nonatomic, readonly) NSDictionary<NSString *, NSNumber *> *interactionIndexMap;
|
||||
// We have to track interactionIds separately. We can't just use interactionIndexMap.allKeys,
|
||||
// as that won't preserve ordering.
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *interactionIds;
|
||||
@property (nonatomic, readonly, nullable) NSNumber *unreadIndicatorIndex;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface ConversationUpdateItem : NSObject
|
||||
|
||||
@property (nonatomic, readonly) ConversationUpdateItemType updateItemType;
|
||||
|
@ -82,7 +95,7 @@ typedef NS_ENUM(NSUInteger, ConversationUpdateItemType) {
|
|||
|
||||
@interface ConversationViewModel : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSArray<id<ConversationViewItem>> *viewItems;
|
||||
@property (nonatomic, readonly) ConversationViewState *viewState;
|
||||
@property (nonatomic, nullable) NSString *focusMessageIdOnOpen;
|
||||
@property (nonatomic, readonly, nullable) ThreadDynamicInteractions *dynamicInteractions;
|
||||
|
||||
|
|
|
@ -44,6 +44,50 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
#pragma mark -
|
||||
|
||||
@implementation ConversationViewState
|
||||
|
||||
- (instancetype)initWithViewItems:(NSArray<id<ConversationViewItem>> *)viewItems
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_viewItems = viewItems;
|
||||
NSMutableDictionary<NSString *, NSNumber *> *interactionIndexMap = [NSMutableDictionary new];
|
||||
NSMutableArray<NSString *> *interactionIds = [NSMutableArray new];
|
||||
for (NSUInteger i = 0; i < self.viewItems.count; i++) {
|
||||
id<ConversationViewItem> viewItem = self.viewItems[i];
|
||||
interactionIndexMap[viewItem.interaction.uniqueId] = @(i);
|
||||
[interactionIds addObject:viewItem.interaction.uniqueId];
|
||||
|
||||
if (viewItem.unreadIndicator != nil) {
|
||||
_unreadIndicatorIndex = @(i);
|
||||
}
|
||||
}
|
||||
_interactionIndexMap = [interactionIndexMap copy];
|
||||
_interactionIds = [interactionIds copy];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable id<ConversationViewItem>)unreadIndicatorViewItem
|
||||
{
|
||||
if (self.unreadIndicatorIndex == nil) {
|
||||
return nil;
|
||||
}
|
||||
NSUInteger index = self.unreadIndicatorIndex.unsignedIntegerValue;
|
||||
if (index >= self.viewItems.count) {
|
||||
OWSFailDebug(@"Invalid index.");
|
||||
return nil;
|
||||
}
|
||||
return self.viewItems[index];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation ConversationUpdateItem
|
||||
|
||||
- (instancetype)initWithUpdateItemType:(ConversationUpdateItemType)updateItemType
|
||||
|
@ -150,7 +194,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
// * Afterward, we must prod the view controller to update layout & view state.
|
||||
@property (nonatomic) ConversationMessageMapping *messageMapping;
|
||||
|
||||
@property (nonatomic) NSArray<id<ConversationViewItem>> *viewItems;
|
||||
@property (nonatomic) ConversationViewState *viewState;
|
||||
@property (nonatomic) NSMutableDictionary<NSString *, id<ConversationViewItem>> *viewItemCache;
|
||||
|
||||
@property (nonatomic, nullable) ThreadDynamicInteractions *dynamicInteractions;
|
||||
|
@ -187,6 +231,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
_persistedViewItems = @[];
|
||||
_unsavedOutgoingMessages = @[];
|
||||
self.focusMessageIdOnOpen = focusMessageIdOnOpen;
|
||||
_viewState = [[ConversationViewState alloc] initWithViewItems:@[]];
|
||||
|
||||
[self configure];
|
||||
|
||||
|
@ -492,22 +537,12 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
}
|
||||
}
|
||||
|
||||
- (nullable id<ConversationViewItem>)viewItemForUnreadMessagesIndicator
|
||||
{
|
||||
for (id<ConversationViewItem> viewItem in self.viewItems) {
|
||||
if (viewItem.unreadIndicator) {
|
||||
return viewItem;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)clearUnreadMessagesIndicator
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
// TODO: Remove by making unread indicator a view model concern.
|
||||
id<ConversationViewItem> _Nullable oldIndicatorItem = [self viewItemForUnreadMessagesIndicator];
|
||||
id<ConversationViewItem> _Nullable oldIndicatorItem = [self.viewState unreadIndicatorViewItem];
|
||||
if (oldIndicatorItem) {
|
||||
// TODO ideally this would be happening within the *same* transaction that caused the unreadMessageIndicator
|
||||
// to be cleared.
|
||||
|
@ -613,10 +648,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
}
|
||||
}
|
||||
|
||||
NSMutableArray<NSString *> *oldItemIdList = [NSMutableArray new];
|
||||
for (id<ConversationViewItem> viewItem in self.viewItems) {
|
||||
[oldItemIdList addObject:viewItem.itemId];
|
||||
}
|
||||
NSArray<NSString *> *oldItemIdList = self.viewState.interactionIds;
|
||||
|
||||
// We need to reload any modified interactions _before_ we call
|
||||
// reloadViewItems.
|
||||
|
@ -655,7 +687,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
return;
|
||||
}
|
||||
|
||||
OWSLogVerbose(@"self.viewItems.count: %zd -> %zd", oldItemIdList.count, self.viewItems.count);
|
||||
OWSLogVerbose(@"self.viewItems.count: %zd -> %zd", oldItemIdList.count, self.viewState.viewItems.count);
|
||||
|
||||
[self updateViewWithOldItemIdList:oldItemIdList updatedItemSet:updatedItemSet];
|
||||
}
|
||||
|
@ -668,10 +700,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
|
||||
OWSLogVerbose(@"");
|
||||
|
||||
NSMutableArray<NSString *> *oldItemIdList = [NSMutableArray new];
|
||||
for (id<ConversationViewItem> viewItem in self.viewItems) {
|
||||
[oldItemIdList addObject:viewItem.itemId];
|
||||
}
|
||||
NSArray<NSString *> *oldItemIdList = self.viewState.interactionIds;
|
||||
|
||||
if (![self reloadViewItems]) {
|
||||
// These errors are rare.
|
||||
|
@ -682,7 +711,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
return;
|
||||
}
|
||||
|
||||
OWSLogVerbose(@"self.viewItems.count: %zd -> %zd", oldItemIdList.count, self.viewItems.count);
|
||||
OWSLogVerbose(@"self.viewItems.count: %zd -> %zd", oldItemIdList.count, self.viewState.viewItems.count);
|
||||
|
||||
[self updateViewWithOldItemIdList:oldItemIdList updatedItemSet:[NSSet set]];
|
||||
}
|
||||
|
@ -698,10 +727,9 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
return;
|
||||
}
|
||||
|
||||
NSMutableArray<NSString *> *newItemIdList = [NSMutableArray new];
|
||||
NSArray<NSString *> *newItemIdList = self.viewState.interactionIds;
|
||||
NSMutableDictionary<NSString *, id<ConversationViewItem>> *newViewItemMap = [NSMutableDictionary new];
|
||||
for (id<ConversationViewItem> viewItem in self.viewItems) {
|
||||
[newItemIdList addObject:viewItem.itemId];
|
||||
for (id<ConversationViewItem> viewItem in self.viewState.viewItems) {
|
||||
newViewItemMap[viewItem.itemId] = viewItem;
|
||||
}
|
||||
|
||||
|
@ -1510,7 +1538,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
viewItem.senderName = senderName;
|
||||
}
|
||||
|
||||
self.viewItems = viewItems;
|
||||
self.viewState = [[ConversationViewState alloc] initWithViewItems:viewItems];
|
||||
self.viewItemCache = viewItemCache;
|
||||
|
||||
return !hasError;
|
||||
|
@ -1661,7 +1689,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
|
||||
// Update the view items if necessary.
|
||||
// We don't have to do this if they haven't been configured yet.
|
||||
if (didChange && self.viewItems != nil) {
|
||||
if (didChange && self.viewState.viewItems != nil) {
|
||||
// When we receive an incoming message, we clear any typing indicators
|
||||
// from that sender. Ideally, we'd like both changes (disappearance of
|
||||
// the typing indicators, appearance of the incoming message) to show up
|
||||
|
|
|
@ -36,7 +36,7 @@ class MenuActionsViewController: UIViewController, MenuActionSheetDelegate {
|
|||
weak var delegate: MenuActionsViewControllerDelegate?
|
||||
|
||||
@objc
|
||||
public let focusedInteraction: TSInteraction?
|
||||
public let focusedInteraction: TSInteraction
|
||||
|
||||
private let focusedView: UIView
|
||||
private let actionSheetView: MenuActionSheetView
|
||||
|
@ -46,7 +46,7 @@ class MenuActionsViewController: UIViewController, MenuActionSheetDelegate {
|
|||
}
|
||||
|
||||
@objc
|
||||
required init(focusedInteraction: TSInteraction?, focusedView: UIView, actions: [MenuAction]) {
|
||||
required init(focusedInteraction: TSInteraction, focusedView: UIView, actions: [MenuAction]) {
|
||||
self.focusedView = focusedView
|
||||
self.focusedInteraction = focusedInteraction
|
||||
|
||||
|
@ -180,7 +180,7 @@ class MenuActionsViewController: UIViewController, MenuActionSheetDelegate {
|
|||
public let vSpacing: CGFloat = 10
|
||||
|
||||
@objc
|
||||
public func focusUI() -> UIView {
|
||||
public var focusUI: UIView {
|
||||
return actionSheetView
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue