From 83cc102f989fa0a5ebe882693e0ce4b4e905b1f8 Mon Sep 17 00:00:00 2001 From: Frederic Jacobs Date: Sat, 6 Dec 2014 17:45:42 +0100 Subject: [PATCH] Immediate feedback on send + unread count badges. --- .../textsecure/Messages/TSIncomingMessage.m | 4 +- .../Messages/TSMessagesManager+sendMessages.m | 23 +++-- .../src/textsecure/Storage/TSDatabaseView.h | 3 + .../src/textsecure/Storage/TSDatabaseView.m | 65 ++++++++++--- .../src/textsecure/Storage/TSStorageManager.m | 24 ++--- .../view controllers/MessagesViewController.m | 93 ++++++++++++++----- .../view controllers/SignalTabBarController.m | 41 ++++++-- .../src/view controllers/TSMessageAdapter.m | 6 +- 8 files changed, 193 insertions(+), 66 deletions(-) diff --git a/Signal/src/textsecure/Messages/TSIncomingMessage.m b/Signal/src/textsecure/Messages/TSIncomingMessage.m index 2eed31ead..65c530074 100644 --- a/Signal/src/textsecure/Messages/TSIncomingMessage.m +++ b/Signal/src/textsecure/Messages/TSIncomingMessage.m @@ -20,7 +20,7 @@ if (self) { _authorId = authorId; - _read = false; + _read = NO; } return self; @@ -35,7 +35,7 @@ if (self) { _authorId = nil; - _read = false; + _read = NO; } return self; diff --git a/Signal/src/textsecure/Messages/TSMessagesManager+sendMessages.m b/Signal/src/textsecure/Messages/TSMessagesManager+sendMessages.m index 2f8a57a03..4b849eb12 100644 --- a/Signal/src/textsecure/Messages/TSMessagesManager+sendMessages.m +++ b/Signal/src/textsecure/Messages/TSMessagesManager+sendMessages.m @@ -52,6 +52,8 @@ dispatch_queue_t sendingQueue() { } - (void)sendMessage:(TSOutgoingMessage*)message inThread:(TSThread*)thread{ + [self saveMessage:message withState:TSOutgoingMessageStateAttemptingOut]; + dispatch_async(sendingQueue(), ^{ if ([thread isKindOfClass:[TSGroupThread class]]) { NSLog(@"Currently unsupported"); @@ -85,7 +87,7 @@ dispatch_queue_t sendingQueue() { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [recipient saveWithTransaction:transaction]; }]; - [self handleMessageSent:message inThread:thread]; + [self handleMessageSent:message]; } failure:^(NSURLSessionDataTask *task, NSError *error) { NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; @@ -116,18 +118,12 @@ dispatch_queue_t sendingQueue() { }]; }]; } else{ - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [message setMessageState:TSOutgoingMessageStateUnsent]; - [message saveWithTransaction:transaction]; - }]; + [self saveMessage:message withState:TSOutgoingMessageStateUnsent]; } } -- (void)handleMessageSent:(TSOutgoingMessage*)message inThread:(TSThread*)thread{ - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [message setMessageState:TSOutgoingMessageStateSent]; - [message saveWithTransaction:transaction]; - }]; +- (void)handleMessageSent:(TSOutgoingMessage*)message { + [self saveMessage:message withState:TSOutgoingMessageStateSent]; } - (void)outgoingMessages:(TSOutgoingMessage*)message toRecipient:(TSRecipient*)recipient completion:(messagesQueue)sendMessages{ @@ -211,6 +207,13 @@ dispatch_queue_t sendingQueue() { return TSUnknownMessageType; } +- (void)saveMessage:(TSOutgoingMessage*)message withState:(TSOutgoingMessageState)state{ + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [message setMessageState:state]; + [message saveWithTransaction:transaction]; + }]; +} + - (NSData*)plainTextForMessage:(TSOutgoingMessage*)message{ PushMessageContentBuilder *builder = [PushMessageContentBuilder new]; diff --git a/Signal/src/textsecure/Storage/TSDatabaseView.h b/Signal/src/textsecure/Storage/TSDatabaseView.h index d133491c6..7868e3e01 100644 --- a/Signal/src/textsecure/Storage/TSDatabaseView.h +++ b/Signal/src/textsecure/Storage/TSDatabaseView.h @@ -12,11 +12,14 @@ extern NSString *TSInboxGroup; extern NSString *TSArchiveGroup; +extern NSString *TSUnreadIncomingMessagesGroup; extern NSString *TSThreadDatabaseViewExtensionName; extern NSString *TSMessageDatabaseViewExtensionName; +extern NSString *TSUnreadDatabaseViewExtensionName; + (BOOL)registerThreadDatabaseView; + (BOOL)registerBuddyConversationDatabaseView; ++ (BOOL)registerUnreadDatabaseView; @end diff --git a/Signal/src/textsecure/Storage/TSDatabaseView.m b/Signal/src/textsecure/Storage/TSDatabaseView.m index 573a4fa54..3d2fd266d 100644 --- a/Signal/src/textsecure/Storage/TSDatabaseView.m +++ b/Signal/src/textsecure/Storage/TSDatabaseView.m @@ -11,18 +11,53 @@ #import #import "TSThread.h" +#import "TSIncomingMessage.h" #import "TSInteraction.h" #import "TSStorageManager.h" #import "TSRecipient.h" -NSString *TSInboxGroup = @"TSInboxGroup"; -NSString *TSArchiveGroup = @"TSArchiveGroup"; +NSString *TSInboxGroup = @"TSInboxGroup"; +NSString *TSArchiveGroup = @"TSArchiveGroup"; + +NSString *TSUnreadIncomingMessagesGroup = @"TSUnreadIncomingMessagesGroup"; NSString *TSThreadDatabaseViewExtensionName = @"TSThreadDatabaseViewExtensionName"; NSString *TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionName"; +NSString *TSUnreadDatabaseViewExtensionName = @"TSUnreadDatabaseViewExtensionName"; @implementation TSDatabaseView ++ (BOOL)registerUnreadDatabaseView { + + YapDatabaseView *unreadView = [[TSStorageManager sharedManager].database registeredExtension:TSUnreadDatabaseViewExtensionName]; + if (unreadView) { + return YES; + } + + YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(NSString *collection, NSString *key, id object) { + if ([object isKindOfClass:[TSIncomingMessage class]]){ + TSIncomingMessage *message = (TSIncomingMessage*)object; + if (message.read == NO){ + return message.uniqueThreadId; + } + } + return nil; + }]; + + YapDatabaseViewSorting *viewSorting = [self messagesSorting]; + + YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init]; + options.isPersistent = YES; + options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[TSInteraction collection]]]; + + YapDatabaseView *view = [[YapDatabaseView alloc] initWithGrouping:viewGrouping + sorting:viewSorting + versionTag:@"1" + options:options]; + + return [[TSStorageManager sharedManager].database registerExtension:view withName:TSUnreadDatabaseViewExtensionName]; +} + + (BOOL)registerThreadDatabaseView { YapDatabaseView *threadView = [[TSStorageManager sharedManager].database registeredExtension:TSThreadDatabaseViewExtensionName]; if (threadView) { @@ -69,17 +104,7 @@ NSString *TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionN return nil; }]; - YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) { - - if ([object1 isKindOfClass:[TSInteraction class]] && [object2 isKindOfClass:[TSInteraction class]]) { - TSInteraction *message1 = (TSInteraction*)object1; - TSInteraction *message2 = (TSInteraction*)object2; - - return [message1.date compare:message2.date]; - } - - return NSOrderedSame; - }]; + YapDatabaseViewSorting *viewSorting = [self messagesSorting]; YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init]; options.isPersistent = YES; @@ -128,4 +153,18 @@ NSString *TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionN } ++ (YapDatabaseViewSorting*)messagesSorting { + return [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) { + + if ([object1 isKindOfClass:[TSInteraction class]] && [object2 isKindOfClass:[TSInteraction class]]) { + TSInteraction *message1 = (TSInteraction*)object1; + TSInteraction *message2 = (TSInteraction*)object2; + + return [message1.date compare:message2.date]; + } + + return NSOrderedSame; + }]; +} + @end diff --git a/Signal/src/textsecure/Storage/TSStorageManager.m b/Signal/src/textsecure/Storage/TSStorageManager.m index cc83cf3d3..29d09e9bc 100644 --- a/Signal/src/textsecure/Storage/TSStorageManager.m +++ b/Signal/src/textsecure/Storage/TSStorageManager.m @@ -51,13 +51,13 @@ static NSString * keychainDBPassAccount = @"TSDatabasePass"; }; _database = [[YapDatabase alloc] initWithPath:[self dbPath] - objectSerializer:NULL - objectDeserializer:NULL - metadataSerializer:NULL - metadataDeserializer:NULL - objectSanitizer:NULL - metadataSanitizer:NULL - options:options]; + objectSerializer:NULL + objectDeserializer:NULL + metadataSerializer:NULL + metadataDeserializer:NULL + objectSanitizer:NULL + metadataSanitizer:NULL + options:options]; _dbConnection = self.newDatabaseConnection; return self; } @@ -65,6 +65,8 @@ static NSString * keychainDBPassAccount = @"TSDatabasePass"; - (void)setupDatabase { [TSDatabaseView registerThreadDatabaseView]; [TSDatabaseView registerBuddyConversationDatabaseView]; + [TSDatabaseView registerUnreadDatabaseView]; + [self.database registerExtension:[[YapDatabaseRelationship alloc] init] withName:@"TSRelationships"]; } @@ -73,16 +75,16 @@ static NSString * keychainDBPassAccount = @"TSDatabasePass"; */ - (void)protectDatabaseFile{ - + NSDictionary *attrs = @{NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication}; NSError *error; - - + + [NSFileManager.defaultManager setAttributes:attrs ofItemAtPath:[self dbPath] error:&error]; [[NSURL fileURLWithPath:[self dbPath]] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&error]; - + if (error) { DDLogError(@"Error while removing log files from backup: %@", error.description); UIAlertView *alert = [[UIAlertView alloc]initWithTitle:NSLocalizedString(@"WARNING", @"") diff --git a/Signal/src/view controllers/MessagesViewController.m b/Signal/src/view controllers/MessagesViewController.m index 75420f991..42ebf18df 100644 --- a/Signal/src/view controllers/MessagesViewController.m +++ b/Signal/src/view controllers/MessagesViewController.m @@ -33,6 +33,7 @@ #import #import "TSInteraction.h" #import "TSMessageAdapter.h" +#import "TSIncomingMessage.h" #import "TSMessagesManager+sendMessages.h" #import "NSDate+millisecondTimeStamp.h" @@ -55,18 +56,21 @@ typedef enum : NSUInteger { } @property (nonatomic, retain) TSThread *thread; -@property (nonatomic, strong) YapDatabaseConnection *uiDatabaseConnection; +@property (nonatomic, strong) YapDatabaseConnection *editingDatabaseConnection; +@property (nonatomic, strong) YapDatabaseConnection *uiDatabaseConnection; @property (nonatomic, strong) YapDatabaseViewMappings *messageMappings; -@property (nonatomic, retain) JSQMessagesBubbleImage *outgoingBubbleImageData; -@property (nonatomic, retain) JSQMessagesBubbleImage *incomingBubbleImageData; -@property (nonatomic, retain) JSQMessagesBubbleImage *outgoingMessageFailedImageData; +@property (nonatomic, retain) JSQMessagesBubbleImage *outgoingBubbleImageData; +@property (nonatomic, retain) JSQMessagesBubbleImage *incomingBubbleImageData; +@property (nonatomic, retain) JSQMessagesBubbleImage *outgoingMessageFailedImageData; + +@property (nonatomic, retain) NSTimer *readTimer; @end @implementation MessagesViewController - (void)setupWithTSIdentifier:(NSString *)identifier{ - [[TSStorageManager sharedManager].newDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { self.thread = [TSContactThread threadWithContactId:identifier transaction:transaction]; }]; } @@ -77,17 +81,21 @@ typedef enum : NSUInteger { - (void)viewDidLoad { [super viewDidLoad]; + [self markAllMessagesAsRead]; isGroupConversation = NO; // TODO: Support Group Conversations [self initializeBubbles]; - self.messageMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[self.thread.uniqueId] view:TSMessageDatabaseViewExtensionName]; + self.messageMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[self.thread.uniqueId] + view:TSMessageDatabaseViewExtensionName]; [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [self.messageMappings updateWithTransaction:transaction]; }]; + self.readTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(markAllMessagesAsRead) userInfo:nil repeats:YES]; + [self initializeNavigationBar]; [self initializeCollectionViewLayout]; @@ -95,13 +103,6 @@ typedef enum : NSUInteger { self.senderDisplayName = ME_MESSAGE_IDENTIFIER } -- (void)didPressBack{ - [self dismissViewControllerAnimated:YES completion:^{ - [self.navigationController.parentViewController.presentingViewController.navigationController pushViewController:self animated:NO]; - }]; -} - - - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @@ -156,6 +157,7 @@ typedef enum : NSUInteger { -(void)showFingerprint { + [self markAllMessagesAsRead]; [self performSegueWithIdentifier:@"fingerprintSegue" sender:self]; } @@ -271,8 +273,7 @@ typedef enum : NSUInteger { -(JSQMessagesCollectionViewCell*)loadIncomingMessageCellForMessage:(id)message atIndexPath:(NSIndexPath*)indexPath { JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:self.collectionView cellForItemAtIndexPath:indexPath]; - if (!message.isMediaMessage) - { + if (!message.isMediaMessage) { cell.textView.textColor = [UIColor blackColor]; cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : cell.textView.textColor, NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) }; @@ -412,8 +413,7 @@ typedef enum : NSUInteger { { TSMessageAdapter * msg = [self messageAtIndexPath:indexPath]; - if (msg.messageType == TSOutgoingMessageAdapter) - { + if (msg.messageType == TSOutgoingMessageAdapter) { return 16.0f; } @@ -565,7 +565,7 @@ typedef enum : NSUInteger { NSError *err = NULL; CMTime time = CMTimeMake(2, 1); CGImageRef snapshotRef = [generate1 copyCGImageAtTime:time actualTime:NULL error:&err]; - __unused UIImage *snapshot = [[UIImage alloc] initWithCGImage:snapshotRef]; + __unused UIImage *snapshot = [[UIImage alloc] initWithCGImage:snapshotRef]; JSQVideoMediaItem * videoItem = [[JSQVideoMediaItem alloc] initWithFileURL:videoURL isReadyToPlay:YES]; JSQMessage * videoMessage = [JSQMessage messageWithSenderId:self.senderId @@ -591,8 +591,7 @@ typedef enum : NSUInteger { #pragma mark Storage access -- (YapDatabaseConnection *)uiDatabaseConnection -{ +- (YapDatabaseConnection*)uiDatabaseConnection { NSAssert([NSThread isMainThread], @"Must access uiDatabaseConnection on main thread!"); if (!_uiDatabaseConnection) { _uiDatabaseConnection = [[TSStorageManager sharedManager] newDatabaseConnection]; @@ -605,6 +604,13 @@ typedef enum : NSUInteger { return _uiDatabaseConnection; } +- (YapDatabaseConnection*)editingDatabaseConnection { + if (!_editingDatabaseConnection) { + _editingDatabaseConnection = [[TSStorageManager sharedManager] newDatabaseConnection]; + } + return _editingDatabaseConnection; +} + - (void)yapDatabaseModified:(NSNotification *)notification { // Process the notification(s), @@ -617,7 +623,34 @@ typedef enum : NSUInteger { rowChanges:&messageRowChanges forNotifications:notifications withMappings:self.messageMappings]; - [self.collectionView reloadData]; + for (YapDatabaseViewRowChange *rowChange in messageRowChanges) + { + switch (rowChange.type) + { + case YapDatabaseViewChangeDelete : + { + [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]]; + break; + } + case YapDatabaseViewChangeInsert : + { + [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; + break; + } + case YapDatabaseViewChangeMove : + { + [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath]]; + [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath]]; + break; + } + case YapDatabaseViewChangeUpdate : + { + [self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]]; + break; + } + } + } + [self finishReceivingMessage]; } @@ -685,4 +718,22 @@ typedef enum : NSUInteger { }]; } +- (void)markAllMessagesAsRead { + [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSUnreadDatabaseViewExtensionName]; + NSUInteger numberOfItemsInSection = [viewTransaction numberOfItemsInGroup:self.thread.uniqueId]; + [self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *writeTransaction) { + for (NSUInteger i = 0; i < numberOfItemsInSection; i++) { + TSIncomingMessage *message = [viewTransaction objectAtIndex:i inGroup:self.thread.uniqueId]; + message.read = YES; + [message saveWithTransaction:writeTransaction]; + } + }]; + }]; +} + +- (void)viewWillDisappear:(BOOL)animated{ + [self.readTimer invalidate]; +} + @end diff --git a/Signal/src/view controllers/SignalTabBarController.m b/Signal/src/view controllers/SignalTabBarController.m index 9d83c6409..d8fe5e343 100644 --- a/Signal/src/view controllers/SignalTabBarController.m +++ b/Signal/src/view controllers/SignalTabBarController.m @@ -6,29 +6,58 @@ // Copyright (c) 2014 Open Whisper Systems. All rights reserved. // +#import +#import + #import "SignalTabBarController.h" + #import "TSAccountManager.h" +#import "TSDatabaseView.h" +#import "TSStorageManager.h" @interface SignalTabBarController () - +@property YapDatabaseConnection *dbConnection; @end @implementation SignalTabBarController - (void)viewDidLoad { [super viewDidLoad]; -} - --(void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; + if (![TSAccountManager isRegistered]){ [self performSegueWithIdentifier:@"showSignupFlow" sender:self]; } + + self.dbConnection = [TSStorageManager sharedManager].newDatabaseConnection; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(yapDatabaseModified:) + name:YapDatabaseModifiedNotification + object:nil]; +} + +- (void)yapDatabaseModified:(NSNotification *)notification { + __block NSUInteger numberOfItems; + [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + numberOfItems = [[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInAllGroups]; + }]; + + NSNumber *badgeNumber = [NSNumber numberWithUnsignedInteger:numberOfItems]; + NSString *badgeValue = nil; + + if (![badgeNumber isEqualToNumber:@0]) { + badgeValue = [badgeNumber stringValue]; + } + [[self signalsItem] setBadgeValue:badgeValue]; + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:badgeNumber.integerValue]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } +- (UITabBarItem*)signalsItem{ + return self.tabBar.items[1]; +} + @end diff --git a/Signal/src/view controllers/TSMessageAdapter.m b/Signal/src/view controllers/TSMessageAdapter.m index b5262faa1..c7b55d67b 100644 --- a/Signal/src/view controllers/TSMessageAdapter.m +++ b/Signal/src/view controllers/TSMessageAdapter.m @@ -60,7 +60,7 @@ NSString *contactId = ((TSContactThread*)thread).contactIdentifier; adapter.senderId = contactId; adapter.senderDisplayName = contactId; - adapter.messageType = TSIncomingMessageAdapter; + adapter.messageType = TSIncomingMessageAdapter; } else { adapter.senderId = ME_MESSAGE_IDENTIFIER; adapter.senderDisplayName = @"Me"; @@ -86,12 +86,12 @@ } else if ([interaction isKindOfClass:[TSInfoMessage class]]){ TSInfoMessage * infoMessage = (TSInfoMessage*)interaction; adapter.infoMessageType = infoMessage.messageType; - adapter.messageBody = @"Placeholder for InfoMessage"; + adapter.messageBody = infoMessage.description; adapter.messageType = TSInfoMessageAdapter; } else { TSErrorMessage * errorMessage = (TSErrorMessage*)interaction; adapter.infoMessageType = errorMessage.errorType; - adapter.messageBody = @"Placeholder for ErrorMessage"; + adapter.messageBody = errorMessage.description; adapter.messageType = TSErrorMessageAdapter; }