mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
MessagesVC: Paging and fix scrolling bug.
Reviewed-by: @FredericJacobs
This commit is contained in:
parent
9ae4a435a1
commit
ee07490d3e
4 changed files with 220 additions and 17 deletions
|
@ -505,6 +505,7 @@
|
|||
FC4FA0331A1D46AE00DA100A /* InitialViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC4FA0321A1D46AE00DA100A /* InitialViewController.m */; };
|
||||
FC5CDF391A3393DD00B47253 /* error_white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FC5CDF371A3393DD00B47253 /* error_white@2x.png */; };
|
||||
FC5CDF3A1A3393DD00B47253 /* warning_white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FC5CDF381A3393DD00B47253 /* warning_white@2x.png */; };
|
||||
FC7C7A961A581AF40091823B /* TSAdapterCacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FC7C7A951A581AF40091823B /* TSAdapterCacheManager.m */; };
|
||||
FC9120411A39EFB70074545C /* qr@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FC91203F1A39EFB70074545C /* qr@2x.png */; };
|
||||
FC9120431A39F9E00074545C /* qr_scan_fingerprint@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FC9120421A39F9E00074545C /* qr_scan_fingerprint@2x.png */; };
|
||||
FCA52AE61A2B676C00CCADFA /* call_canceled@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FCA52ADE1A2B676C00CCADFA /* call_canceled@2x.png */; };
|
||||
|
@ -1211,6 +1212,8 @@
|
|||
FC4FA0321A1D46AE00DA100A /* InitialViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InitialViewController.m; sourceTree = "<group>"; };
|
||||
FC5CDF371A3393DD00B47253 /* error_white@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "error_white@2x.png"; sourceTree = "<group>"; };
|
||||
FC5CDF381A3393DD00B47253 /* warning_white@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "warning_white@2x.png"; sourceTree = "<group>"; };
|
||||
FC7C7A941A581AF40091823B /* TSAdapterCacheManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSAdapterCacheManager.h; sourceTree = "<group>"; };
|
||||
FC7C7A951A581AF40091823B /* TSAdapterCacheManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSAdapterCacheManager.m; sourceTree = "<group>"; };
|
||||
FC91203F1A39EFB70074545C /* qr@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "qr@2x.png"; sourceTree = "<group>"; };
|
||||
FC9120421A39F9E00074545C /* qr_scan_fingerprint@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "qr_scan_fingerprint@2x.png"; sourceTree = "<group>"; };
|
||||
FCA52ADE1A2B676C00CCADFA /* call_canceled@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "call_canceled@2x.png"; sourceTree = "<group>"; };
|
||||
|
@ -1833,6 +1836,8 @@
|
|||
76EB04FD18170B33006006FC /* Zid.m */,
|
||||
FCC81A961A44558300DFEC7D /* UIDevice+TSHardwareVersion.h */,
|
||||
FCC81A971A44558300DFEC7D /* UIDevice+TSHardwareVersion.m */,
|
||||
FC7C7A941A581AF40091823B /* TSAdapterCacheManager.h */,
|
||||
FC7C7A951A581AF40091823B /* TSAdapterCacheManager.m */,
|
||||
);
|
||||
path = util;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3272,6 +3277,7 @@
|
|||
7038632718F70C0700D4A43F /* CryptoTools.m in Sources */,
|
||||
76EB058C18170B33006006FC /* DnsManager.m in Sources */,
|
||||
B63AF5CB1A1F757900D01AAD /* TSRegisterPrekeysRequest.m in Sources */,
|
||||
FC7C7A961A581AF40091823B /* TSAdapterCacheManager.m in Sources */,
|
||||
B6FAAAE81A41BC6C007FEC1D /* TSAttachmentPointer.m in Sources */,
|
||||
B6B096881A1D25ED008BFAA6 /* TSStorageManager+keyFromIntLong.m in Sources */,
|
||||
B6B50AAB1A4192C500F8F607 /* TSMessagesManager+attachments.m in Sources */,
|
||||
|
|
27
Signal/src/util/TSAdapterCacheManager.h
Normal file
27
Signal/src/util/TSAdapterCacheManager.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// TSAdapterCacheManager.h
|
||||
// Signal
|
||||
//
|
||||
// Created by Dylan Bourgeois on 03/01/15.
|
||||
// Copyright (c) 2015 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class TSMessageAdapter;
|
||||
|
||||
@interface TSAdapterCacheManager : NSObject {
|
||||
NSCache * messageAdaptersCache;
|
||||
}
|
||||
|
||||
@property (nonatomic, retain) NSCache *messageAdaptersCache;
|
||||
|
||||
+ (id)sharedManager;
|
||||
|
||||
- (void)cacheAdapter:(TSMessageAdapter*)adapter forInteractionId:(NSString*)identifier;
|
||||
- (void)clearCacheEntryForInteractionId:(NSString*)identifier;
|
||||
- (TSMessageAdapter*)adapterForInteractionId:(NSString*)identifier;
|
||||
- (BOOL)containsCacheEntryForInteractionId:(NSString*)identifier;
|
||||
|
||||
|
||||
@end
|
58
Signal/src/util/TSAdapterCacheManager.m
Normal file
58
Signal/src/util/TSAdapterCacheManager.m
Normal file
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// TSAdapterCacheManager.m
|
||||
// Signal
|
||||
//
|
||||
// Created by Dylan Bourgeois on 03/01/15.
|
||||
// Copyright (c) 2015 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TSAdapterCacheManager.h"
|
||||
#import "TSMessageAdapter.h"
|
||||
|
||||
@implementation TSAdapterCacheManager
|
||||
|
||||
@synthesize messageAdaptersCache;
|
||||
|
||||
+ (id)sharedManager {
|
||||
static TSAdapterCacheManager *sharedManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedManager = [[self alloc] init];
|
||||
});
|
||||
|
||||
return sharedManager;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
if (self = [super init]) {
|
||||
messageAdaptersCache = [[NSCache alloc]init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)cacheAdapter:(TSMessageAdapter*)adapter forInteractionId:(NSString*)identifier
|
||||
{
|
||||
NSParameterAssert(adapter);
|
||||
NSParameterAssert(identifier);
|
||||
[messageAdaptersCache setObject:adapter forKey:identifier];
|
||||
}
|
||||
|
||||
-(void)clearCacheEntryForInteractionId:(NSString*)identifier
|
||||
{
|
||||
NSParameterAssert(identifier);
|
||||
[messageAdaptersCache removeObjectForKey:identifier];
|
||||
}
|
||||
|
||||
-(TSMessageAdapter*)adapterForInteractionId:(NSString*)identifier
|
||||
{
|
||||
NSParameterAssert(identifier);
|
||||
return [messageAdaptersCache objectForKey:identifier];
|
||||
}
|
||||
|
||||
-(BOOL)containsCacheEntryForInteractionId:(NSString*)identifier
|
||||
{
|
||||
return [messageAdaptersCache objectForKey:identifier] != nil;
|
||||
}
|
||||
|
||||
@end
|
|
@ -54,6 +54,12 @@
|
|||
#import "ContactsManager.h"
|
||||
#import "PreferencesUtil.h"
|
||||
|
||||
#import "TSAdapterCacheManager.h"
|
||||
|
||||
#define kYapDatabaseRangeLength 50
|
||||
#define kYapDatabaseRangeMaxLength 300
|
||||
#define kYapDatabaseRangeMinLength 20
|
||||
|
||||
static NSTimeInterval const kTSMessageSentDateShowTimeInterval = 5 * 60;
|
||||
static NSString *const kUpdateGroupSegueIdentifier = @"updateGroupSegue";
|
||||
static NSString *const kFingerprintSegueIdentifier = @"fingerprintSegue";
|
||||
|
@ -82,6 +88,8 @@ typedef enum : NSUInteger {
|
|||
|
||||
@property (nonatomic, retain) NSIndexPath *lastDeliveredMessageIndexPath;
|
||||
|
||||
@property NSUInteger page;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MessagesViewController
|
||||
|
@ -115,6 +123,7 @@ typedef enum : NSUInteger {
|
|||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
[self markAllMessagesAsRead];
|
||||
|
||||
[self initializeBubbles];
|
||||
|
@ -122,6 +131,10 @@ typedef enum : NSUInteger {
|
|||
self.messageMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[self.thread.uniqueId]
|
||||
view:TSMessageDatabaseViewExtensionName];
|
||||
|
||||
self.page = 0;
|
||||
|
||||
[self updateRangeOptionsForPage:self.page];
|
||||
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
[self.messageMappings updateWithTransaction:transaction];
|
||||
}];
|
||||
|
@ -129,7 +142,6 @@ typedef enum : NSUInteger {
|
|||
[self initializeToolbars];
|
||||
[self initializeCollectionViewLayout];
|
||||
|
||||
|
||||
self.senderId = ME_MESSAGE_IDENTIFIER
|
||||
self.senderDisplayName = ME_MESSAGE_IDENTIFIER
|
||||
|
||||
|
@ -142,8 +154,10 @@ typedef enum : NSUInteger {
|
|||
-(void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
self.automaticallyScrollsToMostRecentMessage = YES;
|
||||
[self scrollToBottomAnimated:NO];
|
||||
|
||||
NSIndexPath * lastCellIndexPath = [NSIndexPath indexPathForRow:(NSInteger)[self.messageMappings numberOfItemsInGroup:self.thread.uniqueId]-1 inSection:0];
|
||||
[self.collectionView scrollToItemAtIndexPath:lastCellIndexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:NO];
|
||||
|
||||
}
|
||||
|
||||
- (void)startReadTimer{
|
||||
|
@ -157,7 +171,6 @@ typedef enum : NSUInteger {
|
|||
- (void)viewDidAppear:(BOOL)animated{
|
||||
[super viewDidAppear:animated];
|
||||
[self startReadTimer];
|
||||
[self scrollToBottomAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated{
|
||||
|
@ -219,7 +232,7 @@ typedef enum : NSUInteger {
|
|||
self.collectionView.showsVerticalScrollIndicator = NO;
|
||||
self.collectionView.showsHorizontalScrollIndicator = NO;
|
||||
|
||||
self.automaticallyScrollsToMostRecentMessage = NO;
|
||||
[self updateLoadEarlierVisible];
|
||||
|
||||
self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero;
|
||||
self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero;
|
||||
|
@ -514,7 +527,6 @@ typedef enum : NSUInteger {
|
|||
TSMessageAdapter *messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
|
||||
TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
|
||||
|
||||
|
||||
switch (messageItem.messageType) {
|
||||
case TSOutgoingMessageAdapter:
|
||||
if (messageItem.messageState == TSOutgoingMessageStateUnsent) {
|
||||
|
@ -558,6 +570,81 @@ typedef enum : NSUInteger {
|
|||
}
|
||||
}
|
||||
|
||||
-(void)collectionView:(JSQMessagesCollectionView *)collectionView header:(JSQMessagesLoadEarlierHeaderView *)headerView didTapLoadEarlierMessagesButton:(UIButton *)sender
|
||||
{
|
||||
if ([self shouldShowLoadEarlierMessages]) {
|
||||
self.page++;
|
||||
}
|
||||
|
||||
NSInteger item = (NSInteger)[self scrollToItem];
|
||||
|
||||
[self updateRangeOptionsForPage:self.page];
|
||||
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
[self.messageMappings updateWithTransaction:transaction];
|
||||
}];
|
||||
|
||||
[self updateLayoutForEarlierMessagesWithOffset:item];
|
||||
|
||||
}
|
||||
|
||||
-(BOOL)shouldShowLoadEarlierMessages
|
||||
{
|
||||
__block BOOL show = YES;
|
||||
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction){
|
||||
show = [self.messageMappings numberOfItemsInGroup:self.thread.uniqueId] < [[transaction ext:TSMessageDatabaseViewExtensionName] numberOfItemsInGroup:self.thread.uniqueId];
|
||||
}];
|
||||
|
||||
return show;
|
||||
}
|
||||
|
||||
-(NSUInteger)scrollToItem
|
||||
{
|
||||
__block NSUInteger item = kYapDatabaseRangeLength*(self.page+1) - [self.messageMappings numberOfItemsInGroup:self.thread.uniqueId];
|
||||
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
|
||||
NSUInteger numberOfVisibleMessages = [self.messageMappings numberOfItemsInGroup:self.thread.uniqueId] ;
|
||||
NSUInteger numberOfTotalMessages = [[transaction ext:TSMessageDatabaseViewExtensionName] numberOfItemsInGroup:self.thread.uniqueId] ;
|
||||
NSUInteger numberOfMessagesToLoad = numberOfTotalMessages - numberOfVisibleMessages ;
|
||||
|
||||
BOOL canLoadFullRange = numberOfMessagesToLoad >= kYapDatabaseRangeLength;
|
||||
|
||||
if (!canLoadFullRange) {
|
||||
item = numberOfMessagesToLoad;
|
||||
}
|
||||
}];
|
||||
|
||||
return item == 0 ? item : item - 1;
|
||||
}
|
||||
|
||||
-(void)updateLoadEarlierVisible
|
||||
{
|
||||
[self setShowLoadEarlierMessagesHeader:[self shouldShowLoadEarlierMessages]];
|
||||
}
|
||||
|
||||
-(void)updateLayoutForEarlierMessagesWithOffset:(NSInteger)offset
|
||||
{
|
||||
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
|
||||
[self.collectionView reloadData];
|
||||
|
||||
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:offset inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
|
||||
|
||||
[self updateLoadEarlierVisible];
|
||||
}
|
||||
|
||||
-(void)updateRangeOptionsForPage:(NSUInteger)page
|
||||
{
|
||||
YapDatabaseViewRangeOptions *rangeOptions = [YapDatabaseViewRangeOptions flexibleRangeWithLength:kYapDatabaseRangeLength*(page+1) offset:0 from:YapDatabaseViewEnd];
|
||||
|
||||
rangeOptions.maxLength = kYapDatabaseRangeMaxLength;
|
||||
rangeOptions.minLength = kYapDatabaseRangeMinLength;
|
||||
|
||||
[self.messageMappings setRangeOptions:rangeOptions forGroup:self.thread.uniqueId];
|
||||
|
||||
}
|
||||
|
||||
#pragma mark Bubble User Actions
|
||||
|
||||
- (void)handleUnsentMessageTap:(TSOutgoingMessage*)message{
|
||||
|
@ -823,11 +910,12 @@ typedef enum : NSUInteger {
|
|||
forNotifications:notifications
|
||||
withMappings:self.messageMappings];
|
||||
|
||||
__block BOOL containsInsertion = NO;
|
||||
|
||||
if (!messageRowChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
[self.collectionView performBatchUpdates:^{
|
||||
|
||||
for (YapDatabaseViewRowChange *rowChange in messageRowChanges)
|
||||
|
@ -836,35 +924,51 @@ typedef enum : NSUInteger {
|
|||
{
|
||||
case YapDatabaseViewChangeDelete :
|
||||
{
|
||||
TSInteraction * interaction = [self interactionAtIndexPath:rowChange.indexPath];
|
||||
[[TSAdapterCacheManager sharedManager] clearCacheEntryForInteractionId:interaction.uniqueId];
|
||||
[self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]];
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeInsert :
|
||||
{
|
||||
TSInteraction * interaction = [self interactionAtIndexPath:rowChange.newIndexPath];
|
||||
[[TSAdapterCacheManager sharedManager] cacheAdapter:[TSMessageAdapter messageViewDataWithInteraction:interaction inThread:self.thread] forInteractionId:interaction.uniqueId];
|
||||
[self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]];
|
||||
containsInsertion = YES;
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeMove :
|
||||
{
|
||||
[self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath]];
|
||||
[self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath]];
|
||||
[self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]];
|
||||
[self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]];
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeUpdate :
|
||||
{
|
||||
NSMutableArray *rowsToUpdate = [@[rowChange.indexPath] mutableCopy];
|
||||
|
||||
if (_lastDeliveredMessageIndexPath) {
|
||||
[rowsToUpdate addObject:_lastDeliveredMessageIndexPath];
|
||||
}
|
||||
|
||||
for (NSIndexPath* indexPath in rowsToUpdate) {
|
||||
TSInteraction * interaction = [self interactionAtIndexPath:indexPath];
|
||||
[[TSAdapterCacheManager sharedManager] cacheAdapter:[TSMessageAdapter messageViewDataWithInteraction:interaction inThread:self.thread] forInteractionId:interaction.uniqueId];
|
||||
}
|
||||
|
||||
[self.collectionView reloadItemsAtIndexPaths:rowsToUpdate];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
[self scrollToBottomAnimated:YES];
|
||||
} completion:^(BOOL success) {
|
||||
if (success) {
|
||||
[self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
if (containsInsertion) {
|
||||
[self scrollToBottomAnimated:YES];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -897,8 +1001,16 @@ typedef enum : NSUInteger {
|
|||
|
||||
- (TSMessageAdapter*)messageAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
|
||||
return [TSMessageAdapter messageViewDataWithInteraction:interaction inThread:self.thread];
|
||||
TSAdapterCacheManager * manager = [TSAdapterCacheManager sharedManager];
|
||||
|
||||
if (![manager containsCacheEntryForInteractionId:interaction.uniqueId]) {
|
||||
[manager cacheAdapter:[TSMessageAdapter messageViewDataWithInteraction:interaction inThread:self.thread] forInteractionId:interaction.uniqueId];
|
||||
}
|
||||
|
||||
return [manager adapterForInteractionId:interaction.uniqueId];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark group action view
|
||||
-(void)didPressGroupMenuButton:(UIButton *)sender
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue