MessagesVC: Paging and fix scrolling bug.

Reviewed-by: @FredericJacobs
This commit is contained in:
dtsbourg 2014-12-31 13:22:40 +01:00 committed by Frederic Jacobs
parent 9ae4a435a1
commit ee07490d3e
4 changed files with 220 additions and 17 deletions

View File

@ -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 */,

View 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

View 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

View File

@ -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,14 +131,17 @@ 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];
}];
[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;
@ -426,7 +439,7 @@ typedef enum : NSUInteger {
-(BOOL)shouldShowMessageStatusAtIndexPath:(NSIndexPath*)indexPath
{
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
if([self.thread isKindOfClass:[TSGroupThread class]]) {
return currentMessage.messageType == TSIncomingMessageAdapter;
@ -513,8 +526,7 @@ 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,48 +910,65 @@ typedef enum : NSUInteger {
forNotifications:notifications
withMappings:self.messageMappings];
__block BOOL containsInsertion = NO;
if (!messageRowChanges) {
return;
}
[self.collectionView performBatchUpdates:^{
for (YapDatabaseViewRowChange *rowChange in messageRowChanges)
{
switch (rowChange.type)
{
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
{