diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 8e4efe0979..c8baf27058 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -302,6 +302,7 @@ 45360B901F9527DA00FA666C /* SearcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8F1F9527DA00FA666C /* SearcherTest.swift */; }; 45360B911F952AA900FA666C /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; }; + 4542DF52208B82E9007B4E76 /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadModel.swift */; }; 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; }; 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454A84032059C787008B8C75 /* MediaTileViewController.swift */; }; 454A965A1FD6017E008D2A0E /* SignalAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D913491F62D4A500722898 /* SignalAttachment.swift */; }; @@ -919,6 +920,7 @@ 45360B8F1F9527DA00FA666C /* SearcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearcherTest.swift; sourceTree = ""; }; 4539B5851F79348F007141FF /* PushRegistrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = ""; }; 453CC0361D08E1A60040EBA3 /* sn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sn; path = translations/sn.lproj/Localizable.strings; sourceTree = ""; }; + 4542DF51208B82E9007B4E76 /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = ""; }; 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = ""; }; 454A84032059C787008B8C75 /* MediaTileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaTileViewController.swift; sourceTree = ""; }; 454A965E1FD60EA2008D2A0E /* OWSFlatButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSFlatButton.swift; path = SignalMessaging/Views/OWSFlatButton.swift; sourceTree = SOURCE_ROOT; }; @@ -1898,6 +1900,7 @@ 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */, 458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */, 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */, + 4542DF51208B82E9007B4E76 /* ThreadModel.swift */, ); path = Models; sourceTree = ""; @@ -3199,6 +3202,7 @@ 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, 34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */, 340FC8B8204DAC8D007AEB0F /* AddToGroupViewController.m in Sources */, + 4542DF52208B82E9007B4E76 /* ThreadModel.swift in Sources */, 341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */, 340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */, 34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */, diff --git a/Signal/src/Models/ThreadModel.swift b/Signal/src/Models/ThreadModel.swift new file mode 100644 index 0000000000..33d73e657d --- /dev/null +++ b/Signal/src/Models/ThreadModel.swift @@ -0,0 +1,40 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public class ThreadViewModel: NSObject { + let hasUnreadMessages: Bool + let lastMessageDate: Date + let isGroupThread: Bool + let threadRecord: TSThread + let unreadCount: UInt + let contactIdentifier: String? + let name: String + let isMuted: Bool + var isContactThread: Bool { + return !isGroupThread + } + + let lastMessageText: String? + + init(thread: TSThread, transaction: YapDatabaseReadTransaction) { + self.threadRecord = thread + self.lastMessageDate = thread.lastMessageDate() + self.isGroupThread = thread.isGroupThread() + self.name = thread.name() + self.isMuted = thread.isMuted + self.lastMessageText = thread.lastMessageText(transaction: transaction) + + if let contactThread = thread as? TSContactThread { + self.contactIdentifier = contactThread.contactIdentifier() + } else { + self.contactIdentifier = nil + } + + self.unreadCount = thread.unreadMessageCount(transaction: transaction) + self.hasUnreadMessages = unreadCount > 0 + } +} diff --git a/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h b/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h index 0f9a586648..097669b620 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h @@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @class TSMessage; @class TSOutgoingMessage; @class TSQuotedMessage; +@class YapDatabaseReadTransaction; @protocol ConversationViewCellDelegate @@ -71,7 +72,7 @@ NS_ASSUME_NONNULL_BEGIN // The width of the collection view. @property (nonatomic) int contentWidth; -- (void)loadForDisplay; +- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction; - (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.m b/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.m index 6235989d40..e82863d8cf 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "ConversationViewCell.h" @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN self.contentWidth = 0; } -- (void)loadForDisplay +- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction { OWSFail(@"%@ This method should be overridden.", self.logTag); } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactOffersCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactOffersCell.m index a86655a7b0..6402fbbf67 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactOffersCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactOffersCell.m @@ -85,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN return NSStringFromClass([self class]); } -- (void)loadForDisplay +- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction { OWSAssert(self.viewItem); OWSAssert([self.viewItem.interaction isKindOfClass:[OWSContactOffersInteraction class]]); diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 49b6b817b5..6c19879c3d 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -150,7 +150,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Load -- (void)loadForDisplay +- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction { OWSAssert(self.viewItem); OWSAssert(self.viewItem.interaction); diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSSystemMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSSystemMessageCell.m index d1d6a48b90..4381b6633f 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSSystemMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSSystemMessageCell.m @@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN return NSStringFromClass([self class]); } -- (void)loadForDisplay +- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction { OWSAssert(self.viewItem); @@ -83,7 +83,7 @@ NS_ASSUME_NONNULL_BEGIN self.imageView.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; self.imageView.tintColor = [self iconColorForInteraction:interaction]; self.titleLabel.textColor = [self textColor]; - [self applyTitleForInteraction:interaction label:self.titleLabel]; + [self applyTitleForInteraction:interaction label:self.titleLabel transaction:transaction]; [self setNeedsLayout]; } @@ -162,7 +162,9 @@ NS_ASSUME_NONNULL_BEGIN return result; } -- (void)applyTitleForInteraction:(TSInteraction *)interaction label:(UILabel *)label +- (void)applyTitleForInteraction:(TSInteraction *)interaction + label:(UILabel *)label + transaction:(YapDatabaseReadTransaction *)transaction { OWSAssert(interaction); OWSAssert(label); @@ -170,22 +172,24 @@ NS_ASSUME_NONNULL_BEGIN // TODO: Should we move the copy generation into this view? if ([interaction isKindOfClass:[TSErrorMessage class]]) { - label.text = interaction.description; + TSErrorMessage *errorMessage = (TSErrorMessage *)interaction; + label.text = [errorMessage previewTextWithTransaction:transaction]; } else if ([interaction isKindOfClass:[TSInfoMessage class]]) { - if ([interaction isKindOfClass:[OWSVerificationStateChangeMessage class]]) { - OWSVerificationStateChangeMessage *message = (OWSVerificationStateChangeMessage *)interaction; - BOOL isVerified = message.verificationState == OWSVerificationStateVerified; + TSInfoMessage *infoMessage = (TSInfoMessage *)interaction; + if ([infoMessage isKindOfClass:[OWSVerificationStateChangeMessage class]]) { + OWSVerificationStateChangeMessage *verificationMessage = (OWSVerificationStateChangeMessage *)infoMessage; + BOOL isVerified = verificationMessage.verificationState == OWSVerificationStateVerified; NSString *displayName = - [[Environment current].contactsManager displayNameForPhoneIdentifier:message.recipientId]; + [[Environment current].contactsManager displayNameForPhoneIdentifier:verificationMessage.recipientId]; NSString *titleFormat = (isVerified - ? (message.isLocalChange + ? (verificationMessage.isLocalChange ? NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_VERIFIED_LOCAL", @"Format for info message indicating that the verification state was verified on " @"this device. Embeds {{user's name or phone number}}.") : NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_VERIFIED_OTHER_DEVICE", @"Format for info message indicating that the verification state was verified on " @"another device. Embeds {{user's name or phone number}}.")) - : (message.isLocalChange + : (verificationMessage.isLocalChange ? NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_NOT_VERIFIED_LOCAL", @"Format for info message indicating that the verification state was unverified on " @"this device. Embeds {{user's name or phone number}}.") @@ -194,10 +198,11 @@ NS_ASSUME_NONNULL_BEGIN @"another device. Embeds {{user's name or phone number}}."))); label.text = [NSString stringWithFormat:titleFormat, displayName]; } else { - label.text = interaction.description; + label.text = [infoMessage previewTextWithTransaction:transaction]; } } else if ([interaction isKindOfClass:[TSCall class]]) { - label.text = interaction.description; + TSCall *call = (TSCall *)interaction; + label.text = [call previewTextWithTransaction:transaction]; } else { OWSFail(@"Unknown interaction type: %@", [interaction class]); label.text = nil; @@ -266,7 +271,11 @@ NS_ASSUME_NONNULL_BEGIN result.height += self.topVMargin; result.height += self.bottomVMargin; - [self applyTitleForInteraction:interaction label:self.titleLabel]; + // FIXME pass in transaction from the uiDBConnection. + [[TSYapDatabaseObject dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + [self applyTitleForInteraction:interaction label:self.titleLabel transaction:transaction]; + }]; + CGFloat maxTitleWidth = (viewWidth - ([self hMargin] * 2.f + [self hSpacing] + [self iconSize])); CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)]; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSUnreadIndicatorCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSUnreadIndicatorCell.m index 88ca54b489..13ceeb8179 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSUnreadIndicatorCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSUnreadIndicatorCell.m @@ -84,7 +84,7 @@ NS_ASSUME_NONNULL_BEGIN return NSStringFromClass([self class]); } -- (void)loadForDisplay +- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction { OWSAssert(self.viewItem); OWSAssert([self.viewItem.interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]); diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 9c7bdb86d6..6531cc68fd 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -4757,7 +4757,9 @@ typedef enum : NSUInteger { } cell.contentWidth = self.layout.contentWidth; - [cell loadForDisplay]; + [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + [cell loadForDisplayWithTransaction:transaction]; + }]; return cell; } diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index b050f9e8b4..ef3ffc1dda 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -3223,7 +3223,7 @@ NS_ASSUME_NONNULL_BEGIN TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:phoneNumber.toE164]; [self sendFakeMessages:messageCount thread:contactThread]; - DDLogError(@"Create fake thread: %@, interactions: %zd", + DDLogError(@"Create fake thread: %@, interactions: %tu", phoneNumber.toE164, contactThread.numberOfInteractions); }]; @@ -3251,7 +3251,7 @@ NS_ASSUME_NONNULL_BEGIN [self sendFakeMessages:batchSize thread:thread isTextOnly:isTextOnly transaction:transaction]; }]; remainder -= batchSize; - DDLogInfo(@"%@ sendFakeMessages %zd / %zd", self.logTag, counter - remainder, counter); + DDLogInfo(@"%@ sendFakeMessages %td / %tu", self.logTag, counter - remainder, counter); } }); } @@ -3263,7 +3263,7 @@ NS_ASSUME_NONNULL_BEGIN isTextOnly:(BOOL)isTextOnly transaction:(YapDatabaseReadWriteTransaction *)transaction { - DDLogInfo(@"%@ sendFakeMessages: %zd", self.logTag, counter); + DDLogInfo(@"%@ sendFakeMessages: %tu", self.logTag, counter); for (NSUInteger i = 0; i < counter; i++) { NSString *randomText = [self randomText]; @@ -3411,7 +3411,7 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(thread); - DDLogInfo(@"%@ injectIncomingMessageInThread: %zd", self.logTag, counter); + DDLogInfo(@"%@ injectIncomingMessageInThread: %tu", self.logTag, counter); NSString *randomText = [self randomText]; NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText]; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.h b/Signal/src/ViewControllers/HomeView/HomeViewCell.h index 4e51d15fc6..054b9fcb5f 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.h +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.h @@ -5,7 +5,8 @@ NS_ASSUME_NONNULL_BEGIN @class OWSContactsManager; -@class TSThread; +@class ThreadViewModel; +@class YapDatabaseReadTransaction; @interface HomeViewCell : UITableViewCell @@ -13,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN + (NSString *)cellReuseIdentifier; -- (void)configureWithThread:(TSThread *)thread +- (void)configureWithThread:(ThreadViewModel *)thread contactsManager:(OWSContactsManager *)contactsManager blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index 7281b65b25..785c4cfc5e 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -12,7 +12,6 @@ #import #import #import -#import NS_ASSUME_NONNULL_BEGIN @@ -27,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) UIView *unreadBadge; @property (nonatomic) UILabel *unreadLabel; -@property (nonatomic, nullable) TSThread *thread; +@property (nonatomic, nullable) ThreadViewModel *thread; @property (nonatomic, nullable) OWSContactsManager *contactsManager; @property (nonatomic, readonly) NSMutableArray *viewConstraints; @@ -143,9 +142,9 @@ NS_ASSUME_NONNULL_BEGIN return NSStringFromClass(self.class); } -- (void)configureWithThread:(TSThread *)thread - contactsManager:(OWSContactsManager *)contactsManager - blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet +- (void)configureWithThread:(ThreadViewModel *)thread + contactsManager:(OWSContactsManager *)contactsManager + blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet { OWSAssertIsOnMainThread(); OWSAssert(thread); @@ -183,7 +182,7 @@ NS_ASSUME_NONNULL_BEGIN self.dateTimeLabel.font = self.unreadFont; } - NSUInteger unreadCount = [[OWSMessageUtils sharedManager] unreadMessagesInThread:thread]; + NSUInteger unreadCount = thread.unreadCount; if (unreadCount == 0) { [self.viewConstraints addObject:[self.payloadView autoPinTrailingToSuperviewMargin]]; } else { @@ -240,18 +239,19 @@ NS_ASSUME_NONNULL_BEGIN return; } - TSThread *thread = self.thread; + ThreadViewModel *thread = self.thread; if (thread == nil) { OWSFail(@"%@ thread should not be nil", self.logTag); self.avatarView.image = nil; return; } - self.avatarView.image = - [OWSAvatarBuilder buildImageForThread:thread diameter:self.avatarSize contactsManager:contactsManager]; + self.avatarView.image = [OWSAvatarBuilder buildImageForThread:thread.threadRecord + diameter:self.avatarSize + contactsManager:contactsManager]; } -- (NSAttributedString *)attributedSnippetForThread:(TSThread *)thread +- (NSAttributedString *)attributedSnippetForThread:(ThreadViewModel *)thread blockedPhoneNumberSet:(NSSet *)blockedPhoneNumberSet { OWSAssert(thread); @@ -285,7 +285,7 @@ NS_ASSUME_NONNULL_BEGIN : [UIColor lightGrayColor]), }]]; } - NSString *displayableText = thread.lastMessageLabel.filterStringForDisplay; + NSString *displayableText = thread.lastMessageText; if (displayableText) { [snippetText appendAttributedString:[[NSAttributedString alloc] initWithString:displayableText @@ -447,7 +447,7 @@ NS_ASSUME_NONNULL_BEGIN self.nameLabel.font = self.nameFont; - TSThread *thread = self.thread; + ThreadViewModel *thread = self.thread; if (thread == nil) { OWSFail(@"%@ thread should not be nil", self.logTag); self.nameLabel.attributedText = nil; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index fec4651188..1d507f74c5 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -47,7 +47,7 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; @property (nonatomic) UISegmentedControl *segmentedControl; @property (nonatomic) id previewingContext; @property (nonatomic) NSSet *blockedPhoneNumberSet; - +@property (nonatomic, readonly) NSCache *threadViewModelCache; @property (nonatomic) BOOL isViewVisible; @property (nonatomic) BOOL isAppInBackground; @property (nonatomic) BOOL shouldObserveDBModifications; @@ -108,6 +108,7 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; _messageSender = [Environment current].messageSender; _blockingManager = [OWSBlockingManager sharedManager]; _blockedPhoneNumberSet = [NSSet setWithArray:[_blockingManager blockedPhoneNumbers]]; + _threadViewModelCache = [NSCache new]; // Ensure ExperienceUpgradeFinder has been initialized. [ExperienceUpgradeFinder sharedManager]; @@ -155,14 +156,14 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; _blockedPhoneNumberSet = [NSSet setWithArray:[_blockingManager blockedPhoneNumbers]]; - [self.tableView reloadData]; + [self reloadTableViewData]; } - (void)signalAccountsDidChange:(id)notification { OWSAssertIsOnMainThread(); - [self.tableView reloadData]; + [self reloadTableViewData]; } #pragma mark - View Life Cycle @@ -395,7 +396,11 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - if ([TSThread numberOfKeysInCollection] > 0) { + __block BOOL hasAnyMessages; + [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { + hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction]; + }]; + if (hasAnyMessages) { [self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [self updateReminderViews]; @@ -468,6 +473,13 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; } } +- (void)reloadTableViewData +{ + // PERF: come up with a more nuanced cache clearing scheme + [self.threadViewModelCache removeAllObjects]; + [self.tableView reloadData]; +} + - (void)resetMappings { // If we're entering "active" mode (e.g. view is visible and app is in foreground), @@ -484,13 +496,18 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; }]; } - [[self tableView] reloadData]; + [self reloadTableViewData]; + [self checkIfEmptyView]; [self updateInboxCountLabel]; // If the user hasn't already granted contact access // we don't want to request until they receive a message. - if ([TSThread numberOfKeysInCollection] > 0) { + __block BOOL hasAnyMessages; + [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { + hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction]; + }]; + if (hasAnyMessages) { [self.contactsManager requestSystemContactsOnce]; } } @@ -506,11 +523,21 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; self.isAppInBackground = YES; } +- (BOOL)hasAnyMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + return [TSThread numberOfKeysInCollectionWithTransaction:transaction] > 0; +} + - (void)applicationDidBecomeActive:(NSNotification *)notification { // It's possible a thread was created while we where in the background. But since we don't honor contact // requests unless the app is in the foregrond, we must check again here upon becoming active. - if ([TSThread numberOfKeysInCollection] > 0) { + __block BOOL hasAnyMessages; + [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { + hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction]; + }]; + + if (hasAnyMessages) { [self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [self updateReminderViews]; @@ -577,13 +604,29 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; return (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)section]; } +- (ThreadViewModel *)threadViewModelForIndexPath:(NSIndexPath *)indexPath +{ + TSThread *threadRecord = [self threadForIndexPath:indexPath]; + + ThreadViewModel *_Nullable cachedThreadViewModel = [self.threadViewModelCache objectForKey:threadRecord.uniqueId]; + if (cachedThreadViewModel) { + return cachedThreadViewModel; + } + + __block ThreadViewModel *_Nullable newThreadViewModel; + [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + newThreadViewModel = [[ThreadViewModel alloc] initWithThread:threadRecord transaction:transaction]; + }]; + [self.threadViewModelCache setObject:newThreadViewModel forKey:threadRecord.uniqueId]; + return newThreadViewModel; +} + - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { HomeViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:HomeViewCell.cellReuseIdentifier]; OWSAssert(cell); - TSThread *thread = [self threadForIndexPath:indexPath]; - + ThreadViewModel *thread = [self threadViewModelForIndexPath:indexPath]; [cell configureWithThread:thread contactsManager:self.contactsManager blockedPhoneNumberSet:self.blockedPhoneNumberSet]; @@ -891,7 +934,7 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; [self resetMappings]; - [[self tableView] reloadData]; + [self reloadTableViewData]; [self checkIfEmptyView]; [self updateReminderViews]; } @@ -904,6 +947,8 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; if (!_uiDatabaseConnection) { _uiDatabaseConnection = [OWSPrimaryStorage.sharedManager newDatabaseConnection]; + // default is 250 + _uiDatabaseConnection.objectCacheLimit = 500; [_uiDatabaseConnection beginLongLivedReadTransaction]; } return _uiDatabaseConnection; @@ -948,7 +993,12 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; // If the user hasn't already granted contact access // we don't want to request until they receive a message. - if ([TSThread numberOfKeysInCollection] > 0) { + __block BOOL hasAnyMessages; + [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction]; + }]; + + if (hasAnyMessages) { [self.contactsManager requestSystemContactsOnce]; } @@ -989,6 +1039,10 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; } for (YapDatabaseViewRowChange *rowChange in rowChanges) { + NSString *key = rowChange.collectionKey.key; + OWSAssert(key); + [self.threadViewModelCache removeObjectForKey:key]; + switch (rowChange.type) { case YapDatabaseViewChangeDelete: { [self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ] diff --git a/Signal/src/environment/NotificationsManager.m b/Signal/src/environment/NotificationsManager.m index a09ab3b657..1bd2f6f740 100644 --- a/Signal/src/environment/NotificationsManager.m +++ b/Signal/src/environment/NotificationsManager.m @@ -18,6 +18,7 @@ #import #import #import +#import @interface NotificationsManager () @@ -200,51 +201,56 @@ #pragma mark - Signal Messages -- (void)notifyUserForErrorMessage:(TSErrorMessage *)message inThread:(TSThread *)thread { +- (void)notifyUserForErrorMessage:(TSErrorMessage *)message + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)transaction +{ OWSAssert(message); OWSAssert(thread); - dispatch_async(dispatch_get_main_queue(), ^{ - if (thread.isMuted) { - return; - } + NSString *messageText = [message previewTextWithTransaction:transaction]; - BOOL shouldPlaySound = [self shouldPlaySoundForNotification]; + [transaction + addCompletionQueue:nil + completionBlock:^() { + if (thread.isMuted) { + return; + } - NSString *messageDescription = message.description; + BOOL shouldPlaySound = [self shouldPlaySoundForNotification]; - if (([UIApplication sharedApplication].applicationState != UIApplicationStateActive) && messageDescription) { - UILocalNotification *notification = [[UILocalNotification alloc] init]; - notification.userInfo = @{ Signal_Thread_UserInfo_Key : thread.uniqueId }; - if (shouldPlaySound) { - OWSSound sound = [OWSSounds notificationSoundForThread:thread]; - notification.soundName = [OWSSounds filenameForSound:sound]; - } + if (([UIApplication sharedApplication].applicationState != UIApplicationStateActive) && messageText) { + UILocalNotification *notification = [[UILocalNotification alloc] init]; + notification.userInfo = @{ Signal_Thread_UserInfo_Key : thread.uniqueId }; + if (shouldPlaySound) { + OWSSound sound = [OWSSounds notificationSoundForThread:thread]; + notification.soundName = [OWSSounds filenameForSound:sound]; + } - NSString *alertBodyString = @""; + NSString *alertBodyString = @""; - NSString *authorName = [thread name]; - switch (self.notificationPreviewType) { - case NotificationNamePreview: - case NotificationNameNoPreview: - alertBodyString = [NSString stringWithFormat:@"%@: %@", authorName, messageDescription]; - break; - case NotificationNoNameNoPreview: - alertBodyString = messageDescription; - break; - } - notification.alertBody = alertBodyString; + NSString *authorName = [thread name]; + switch (self.notificationPreviewType) { + case NotificationNamePreview: + case NotificationNameNoPreview: + alertBodyString = [NSString stringWithFormat:@"%@: %@", authorName, messageText]; + break; + case NotificationNoNameNoPreview: + alertBodyString = messageText; + break; + } + notification.alertBody = alertBodyString; - [[PushManager sharedManager] presentNotification:notification checkForCancel:NO]; - } else { - if (shouldPlaySound && [Environment.preferences soundInForeground]) { - OWSSound sound = [OWSSounds notificationSoundForThread:thread]; - SystemSoundID soundId = [OWSSounds systemSoundIDForSound:sound quiet:YES]; - // Vibrate, respect silent switch, respect "Alert" volume, not media volume. - AudioServicesPlayAlertSound(soundId); - } - } - }); + [[PushManager sharedManager] presentNotification:notification checkForCancel:NO]; + } else { + if (shouldPlaySound && [Environment.preferences soundInForeground]) { + OWSSound sound = [OWSSounds notificationSoundForThread:thread]; + SystemSoundID soundId = [OWSSounds systemSoundIDForSound:sound quiet:YES]; + // Vibrate, respect silent switch, respect "Alert" volume, not media volume. + AudioServicesPlayAlertSound(soundId); + } + } + }]; } - (void)notifyUserForIncomingMessage:(TSIncomingMessage *)message diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 5d6ef6deb9..96a3dc6b23 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -371,9 +371,15 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification { OWSAssert(recipientId.length > 0); - SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; + SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; if (!signalAccount) { - return nil; + // search system contacts for no-longer-registered signal users, for which there will be no SignalAccount + DDLogDebug(@"%@ no signal account", self.logTag); + Contact *_Nullable nonSignalContact = self.allContactsMap[recipientId]; + if (!nonSignalContact) { + return nil; + } + return nonSignalContact.fullName; } NSString *fullName = signalAccount.contactFullName; diff --git a/SignalMessaging/environment/NoopNotificationsManager.swift b/SignalMessaging/environment/NoopNotificationsManager.swift index 865e47c98f..a801a7d8d2 100644 --- a/SignalMessaging/environment/NoopNotificationsManager.swift +++ b/SignalMessaging/environment/NoopNotificationsManager.swift @@ -7,11 +7,11 @@ import SignalServiceKit @objc public class NoopNotificationsManager: NSObject, NotificationsProtocol { - public func notifyUser(for incomingMessage: TSIncomingMessage!, in thread: TSThread!, contactsManager: ContactsManagerProtocol!, transaction: YapDatabaseReadTransaction!) { + public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, contactsManager: ContactsManagerProtocol, transaction: YapDatabaseReadTransaction) { owsFail("\(self.logTag) in \(#function).") } - public func notifyUser(for error: TSErrorMessage!, in thread: TSThread!) { + public func notifyUser(for error: TSErrorMessage, thread: TSThread, transaction: YapDatabaseReadWriteTransaction) { Logger.warn("\(self.logTag) in \(#function), skipping notification for: \(error.description)") } } diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h index 8ed22a61fd..07b795bd98 100644 --- a/SignalServiceKit/src/Contacts/TSThread.h +++ b/SignalServiceKit/src/Contacts/TSThread.h @@ -65,12 +65,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSArray *)receivedMessagesForInvalidKey:(NSData *)key; -/** - * Returns whether or not the thread has unread messages. - * - * @return YES if it has unread TSIncomingMessages, NO otherwise. - */ -- (BOOL)hasUnreadMessages; +- (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction + NS_SWIFT_NAME(unreadMessageCount(transaction:)); - (BOOL)hasSafetyNumbers; @@ -90,7 +86,8 @@ NS_ASSUME_NONNULL_BEGIN * * @return Thread preview string. */ -- (NSString *)lastMessageLabel; +- (NSString *)lastMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction + NS_SWIFT_NAME(lastMessageText(transaction:)); /** * Updates the thread's caches of the latest interaction. diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m index f9cc5fe4ce..074dc7c0d1 100644 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ b/SignalServiceKit/src/Contacts/TSThread.m @@ -4,6 +4,7 @@ #import "TSThread.h" #import "NSDate+OWS.h" +#import "NSString+SSK.h" #import "OWSPrimaryStorage.h" #import "OWSReadTracking.h" #import "TSDatabaseView.h" @@ -24,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy) NSString *messageDraft; @property (atomic, nullable) NSDate *mutedUntilDate; -- (TSInteraction *)lastInteraction; +- (TSInteraction *)lastInteractionWithTranscation:(YapDatabaseReadTransaction *)transaction; @end @@ -199,17 +200,6 @@ NS_ASSUME_NONNULL_BEGIN return count; } -- (BOOL)hasUnreadMessages { - TSInteraction *interaction = self.lastInteraction; - BOOL hasUnread = NO; - - if ([interaction isKindOfClass:[TSIncomingMessage class]]) { - hasUnread = ![(TSIncomingMessage *)interaction wasRead]; - } - - return hasUnread; -} - - (NSArray> *)unseenMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction { NSMutableArray> *messages = [NSMutableArray new]; @@ -228,6 +218,11 @@ NS_ASSUME_NONNULL_BEGIN return [messages copy]; } +- (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + return [[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInGroup:self.uniqueId]; +} + - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { for (id message in [self unseenMessagesWithTransaction:transaction]) { @@ -238,34 +233,34 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert([self unseenMessagesWithTransaction:transaction].count < 1); } -- (TSInteraction *) lastInteraction { - __block TSInteraction *last; - [OWSPrimaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - last = [[transaction ext:TSMessageDatabaseViewExtensionName] lastObjectInGroup:self.uniqueId]; - }]; - return last; -} - -- (TSInteraction *)lastInteractionForInbox +- (TSInteraction *)lastInteractionForInboxWithTransaction:(YapDatabaseReadTransaction *)transaction { + __block NSUInteger missedCount = 0; __block TSInteraction *last = nil; - [OWSPrimaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [[transaction ext:TSMessageDatabaseViewExtensionName] - enumerateRowsInGroup:self.uniqueId - withOptions:NSEnumerationReverse - usingBlock:^( - NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) { + [[transaction ext:TSMessageDatabaseViewExtensionName] + enumerateRowsInGroup:self.uniqueId + withOptions:NSEnumerationReverse + usingBlock:^( + NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) { + + OWSAssert([object isKindOfClass:[TSInteraction class]]); - OWSAssert([object isKindOfClass:[TSInteraction class]]); + missedCount++; + TSInteraction *interaction = (TSInteraction *)object; + + if ([TSThread shouldInteractionAppearInInbox:interaction]) { + last = interaction; - TSInteraction *interaction = (TSInteraction *)object; - - if ([TSThread shouldInteractionAppearInInbox:interaction]) { - last = interaction; - *stop = YES; - } - }]; - }]; + // For long ignored threads, with lots of SN changes this can get really slow. + // I see this in development because I have a lot of long forgotten threads with members + // who's test devices are constantly reinstalled. We could add a purpose-built DB view, + // but I think in the real world this is rare to be a hotspot. + if (missedCount > 50) { + DDLogWarn(@"%@ found last interaction for inbox after skipping %tu items", self.logTag, missedCount); + } + *stop = YES; + } + }]; return last; } @@ -277,12 +272,14 @@ NS_ASSUME_NONNULL_BEGIN } } -- (NSString *)lastMessageLabel { - TSInteraction *interaction = self.lastInteractionForInbox; - if (interaction == nil) { - return @""; +- (NSString *)lastMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + TSInteraction *interaction = [self lastInteractionForInboxWithTransaction:transaction]; + if ([interaction conformsToProtocol:@protocol(OWSPreviewText)]) { + id previewable = (id)interaction; + return [previewable previewTextWithTransaction:transaction].filterStringForDisplay; } else { - return interaction.description; + return @""; } } diff --git a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m index 599ea92022..9b4de20ed2 100644 --- a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m +++ b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m @@ -114,6 +114,7 @@ NS_ASSUME_NONNULL_BEGIN return !![[OWSIdentityManager sharedManager] identityKeyForRecipientId:self.contactIdentifier]; } +// TODO deprecate this? seems weird to access the displayName in the DB model - (NSString *)name { return [[TextSecureKitEnv sharedEnv].contactsManager displayNameForPhoneIdentifier:self.contactIdentifier]; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m b/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m index 1391aa271a..8ae97209dc 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m @@ -56,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN return NO; } -- (NSString *)description +-(NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction { if (self.createdByRemoteName) { if (self.configurationIsEnabled && self.configurationDurationSeconds > 0) { diff --git a/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.m b/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.m index 363605a73b..1f130877a7 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.m @@ -97,7 +97,8 @@ NSUInteger TSErrorMessageSchemaVersion = 1; return OWSInteractionType_Error; } -- (NSString *)description { +- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction +{ switch (_errorType) { case TSErrorMessageNoSession: return NSLocalizedString(@"ERROR_MESSAGE_NO_SESSION", @""); diff --git a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m b/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m index 10abeb18b5..5e19dd4468 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m @@ -91,7 +91,8 @@ NSUInteger TSInfoMessageSchemaVersion = 1; return OWSInteractionType_Info; } -- (NSString *)description { +- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction +{ switch (_messageType) { case TSInfoMessageTypeSessionDidEnd: return NSLocalizedString(@"SECURE_SESSION_RESET", nil); diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction.h b/SignalServiceKit/src/Messages/Interactions/TSInteraction.h index 67044af1fc..a6088803d7 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction.h +++ b/SignalServiceKit/src/Messages/Interactions/TSInteraction.h @@ -19,6 +19,12 @@ typedef NS_ENUM(NSInteger, OWSInteractionType) { OWSInteractionType_Offer, }; +@protocol OWSPreviewText + +- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction; + +@end + @interface TSInteraction : TSYapDatabaseObject - (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread; diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m index b5319d9d0d..bd6a53662e 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m +++ b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m @@ -127,8 +127,10 @@ NS_ASSUME_NONNULL_BEGIN return OWSInteractionType_Unknown; } -- (NSString *)description { - return @"Interaction description"; +- (NSString *)description +{ + return [NSString + stringWithFormat:@"%@ in thread: %@ timestamp: %tu", [super description], self.uniqueThreadId, self.timestamp]; } - (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.h b/SignalServiceKit/src/Messages/Interactions/TSMessage.h index f08f13acfb..66c3598043 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @class TSQuotedMessage; @class YapDatabaseReadWriteTransaction; -@interface TSMessage : TSInteraction +@interface TSMessage : TSInteraction @property (nonatomic, readonly) NSMutableArray *attachmentIds; @property (nonatomic, readonly, nullable) NSString *body; @@ -39,7 +39,6 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)hasAttachments; - (nullable TSAttachment *)attachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction; - (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream; - (BOOL)shouldStartExpireTimer; diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.m b/SignalServiceKit/src/Messages/Interactions/TSMessage.m index 558af46131..071aada166 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.m @@ -265,16 +265,6 @@ static const NSUInteger OWSMessageSchemaVersion = 4; } } -// TODO deprecate this and implement something like previewTextWithTransaction: for all TSInteractions -- (NSString *)description -{ - __block NSString *result; - [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - result = [self previewTextWithTransaction:transaction]; - }]; - return result; -} - - (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { [super removeWithTransaction:transaction]; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index a98b39491a..0e95c93af1 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -437,7 +437,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec [builder setBody:self.body]; } else { OWSFail(@"%@ message body length too long.", self.logTag); - NSMutableString *truncatedBody = [self.body mutableCopy]; + NSString *truncatedBody = self.body; while ([truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kOversizeTextMessageSizeThreshold) { DDLogError(@"%@ truncating body which is too long: %tu", self.logTag, diff --git a/SignalServiceKit/src/Messages/OWSIdentityManager.m b/SignalServiceKit/src/Messages/OWSIdentityManager.m index ed76aa21c0..78e0b60eb0 100644 --- a/SignalServiceKit/src/Messages/OWSIdentityManager.m +++ b/SignalServiceKit/src/Messages/OWSIdentityManager.m @@ -545,7 +545,9 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa [message saveWithTransaction:transaction]; } - [[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage inThread:contactThread]; + [[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage + thread:contactThread + transaction:transaction]; } - (void)enqueueSyncMessageForVerificationStateForRecipientId:(NSString *)recipientId diff --git a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m index 220b36983f..dffbad90ed 100644 --- a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m +++ b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m @@ -269,8 +269,10 @@ NS_ASSUME_NONNULL_BEGIN exception.name, exception.reason); - __block TSErrorMessage *errorMessage; + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + TSErrorMessage *errorMessage; + if ([exception.name isEqualToString:NoSessionException]) { OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorNoSession], envelope); errorMessage = [TSErrorMessage missingSessionWithEnvelope:envelope withTransaction:transaction]; @@ -298,18 +300,21 @@ NS_ASSUME_NONNULL_BEGIN } OWSAssert(errorMessage); - [errorMessage saveWithTransaction:transaction]; + if (errorMessage != nil) { + [errorMessage saveWithTransaction:transaction]; + [self notifyUserForErrorMessage:errorMessage envelope:envelope transaction:transaction]; + } }]; - - if (errorMessage != nil) { - [self notifyForErrorMessage:errorMessage withEnvelope:envelope]; - } } -- (void)notifyForErrorMessage:(TSErrorMessage *)errorMessage withEnvelope:(OWSSignalServiceProtosEnvelope *)envelope +- (void)notifyUserForErrorMessage:(TSErrorMessage *)errorMessage + envelope:(OWSSignalServiceProtosEnvelope *)envelope + transaction:(YapDatabaseReadWriteTransaction *)transaction { - TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:envelope.source]; - [[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage inThread:contactThread]; + TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction]; + [[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage + thread:contactThread + transaction:transaction]; } @end diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.h b/SignalServiceKit/src/Messages/OWSMessageUtils.h index 5c136eebef..031e7fc98d 100644 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.h +++ b/SignalServiceKit/src/Messages/OWSMessageUtils.h @@ -15,7 +15,6 @@ NS_ASSUME_NONNULL_BEGIN - (NSUInteger)unreadMessagesCount; - (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread; -- (NSUInteger)unreadMessagesInThread:(TSThread *)thread; - (void)updateApplicationBadgeCount; diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.m b/SignalServiceKit/src/Messages/OWSMessageUtils.m index 79c72df0c7..165e11f677 100644 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.m +++ b/SignalServiceKit/src/Messages/OWSMessageUtils.m @@ -95,14 +95,6 @@ NS_ASSUME_NONNULL_BEGIN [CurrentAppContext() setMainAppBadgeNumber:numberOfItems]; } -- (NSUInteger)unreadMessagesInThread:(TSThread *)thread -{ - __block NSUInteger numberOfItems; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - numberOfItems = [[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInGroup:thread.uniqueId]; - }]; - return numberOfItems; -} @end diff --git a/SignalServiceKit/src/Messages/TSCall.h b/SignalServiceKit/src/Messages/TSCall.h index bcadbaf8b9..fdca7d4d12 100644 --- a/SignalServiceKit/src/Messages/TSCall.h +++ b/SignalServiceKit/src/Messages/TSCall.h @@ -20,7 +20,7 @@ typedef enum { RPRecentCallTypeIncomingDeclined } RPRecentCallType; -@interface TSCall : TSInteraction +@interface TSCall : TSInteraction @property (nonatomic, readonly) RPRecentCallType callType; diff --git a/SignalServiceKit/src/Messages/TSCall.m b/SignalServiceKit/src/Messages/TSCall.m index 7b37efb3a2..20d805c09f 100644 --- a/SignalServiceKit/src/Messages/TSCall.m +++ b/SignalServiceKit/src/Messages/TSCall.m @@ -67,7 +67,9 @@ NSUInteger TSCallCurrentSchemaVersion = 1; return OWSInteractionType_Call; } -- (NSString *)description { +- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + // We don't actually use the `transaction` but other sibling classes do. switch (_callType) { case RPRecentCallTypeIncoming: return NSLocalizedString(@"INCOMING_CALL", @""); @@ -83,7 +85,7 @@ NSUInteger TSCallCurrentSchemaVersion = 1; return NSLocalizedString(@"INFO_MESSAGE_MISSED_CALL_DUE_TO_CHANGED_IDENITY", @"info message text shown in conversation view"); case RPRecentCallTypeIncomingDeclined: return NSLocalizedString(@"INCOMING_DECLINED_CALL", - @"info message recorded in conversation history when local user declined a call"); + @"info message recorded in conversation history when local user declined a call"); } } diff --git a/SignalServiceKit/src/Protocols/NotificationsProtocol.h b/SignalServiceKit/src/Protocols/NotificationsProtocol.h index 183cec0781..892c80eed6 100644 --- a/SignalServiceKit/src/Protocols/NotificationsProtocol.h +++ b/SignalServiceKit/src/Protocols/NotificationsProtocol.h @@ -1,11 +1,14 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +NS_ASSUME_NONNULL_BEGIN + @class TSErrorMessage; @class TSIncomingMessage; @class TSThread; @class YapDatabaseReadTransaction; + @protocol ContactsManagerProtocol; @protocol NotificationsProtocol @@ -15,6 +18,10 @@ contactsManager:(id)contactsManager transaction:(YapDatabaseReadTransaction *)transaction; -- (void)notifyUserForErrorMessage:(TSErrorMessage *)error inThread:(TSThread *)thread; +- (void)notifyUserForErrorMessage:(TSErrorMessage *)error + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)transaction; @end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSYapDatabaseObject.m b/SignalServiceKit/src/Storage/TSYapDatabaseObject.m index f9eafec5f6..4317e8b7a5 100644 --- a/SignalServiceKit/src/Storage/TSYapDatabaseObject.m +++ b/SignalServiceKit/src/Storage/TSYapDatabaseObject.m @@ -109,17 +109,20 @@ NS_ASSUME_NONNULL_BEGIN + (YapDatabaseConnection *)dbReadConnection { + OWSJanksUI(); + // We use TSYapDatabaseObject's dbReadWriteConnection (not OWSPrimaryStorage's // dbReadConnection) for consistency, since we tend to [TSYapDatabaseObject // save] and want to write to the same connection we read from. To get true // consistency, we'd want to update entities by reading & writing from within // the same transaction, but that'll be a big refactor. - return self.dbReadWriteConnection; } + (YapDatabaseConnection *)dbReadWriteConnection { + OWSJanksUI(); + // Use a dedicated connection for model reads & writes. static YapDatabaseConnection *dbReadWriteConnection = nil; static dispatch_once_t onceToken; diff --git a/SignalServiceKit/src/Util/OWSAsserts.h b/SignalServiceKit/src/Util/OWSAsserts.h index c9f34a660b..84be8e747e 100755 --- a/SignalServiceKit/src/Util/OWSAsserts.h +++ b/SignalServiceKit/src/Util/OWSAsserts.h @@ -150,4 +150,26 @@ void SwiftAssertIsOnMainThread(NSString *functionName); userInfo:userInfoParam]; \ } + +// UI JANK +// +// In pursuit of smooth UI, we want to continue moving blocking operations off the main thread. +// Add `OWSJanksUI` in code paths that shouldn't be called on the main thread. +// Because we have pervasively broken this tenant, enabling it by default would be too disruptive +// but it's helpful while unjanking and maybe someday we can have it enabled by default. +//#define DEBUG_UI_JANK 1 + +#ifdef DEBUG +#ifdef DEBUG_UI_JANK +#define OWSJanksUI() \ + do { \ + OWSAssert(![NSThread isMainThread]) \ + } while (NO) +#endif +#endif + +#ifndef OWSJanksUI +#define OWSJanksUI() +#endif + NS_ASSUME_NONNULL_END