diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 814f5f145..ae06dac77 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -75,6 +75,9 @@ 34B3F89F1E8DF5490035BE1A /* OWSTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F89E1E8DF5490035BE1A /* OWSTableViewController.m */; }; 34B3F8A21E8EA6040035BE1A /* ViewControllerUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */; }; 34C42D5B1F45F7A80072EC04 /* OWSNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D5A1F45F7A80072EC04 /* OWSNavigationController.m */; }; + 34C42D611F4734CA0072EC04 /* OWSContactOffersCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D601F4734CA0072EC04 /* OWSContactOffersCell.m */; }; + 34C42D661F4734ED0072EC04 /* OWSContactOffersInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */; }; + 34C42D671F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D651F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m */; }; 34CCAF381F0C0599004084F4 /* AppUpdateNag.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */; }; 34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */; }; 34CE88E71F2FB9A10098030F /* ProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88E61F2FB9A10098030F /* ProfileViewController.m */; }; @@ -94,7 +97,6 @@ 34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */; }; 34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */; }; 34E8BF381EE9E2FD00F5F4CA /* FingerprintViewScanController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E8BF371EE9E2FD00F5F4CA /* FingerprintViewScanController.m */; }; - 34F3089C1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F3089B1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m */; }; 34F3089F1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F3089E1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m */; }; 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; }; 34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */; }; @@ -508,6 +510,12 @@ 34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewControllerUtils.m; sourceTree = ""; }; 34C42D591F45F7A80072EC04 /* OWSNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSNavigationController.h; sourceTree = ""; }; 34C42D5A1F45F7A80072EC04 /* OWSNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSNavigationController.m; sourceTree = ""; }; + 34C42D5F1F4734CA0072EC04 /* OWSContactOffersCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactOffersCell.h; sourceTree = ""; }; + 34C42D601F4734CA0072EC04 /* OWSContactOffersCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactOffersCell.m; sourceTree = ""; }; + 34C42D621F4734ED0072EC04 /* OWSContactOffersInteraction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactOffersInteraction.h; sourceTree = ""; }; + 34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactOffersInteraction.m; sourceTree = ""; }; + 34C42D641F4734ED0072EC04 /* TSUnreadIndicatorInteraction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSUnreadIndicatorInteraction.h; sourceTree = ""; }; + 34C42D651F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSUnreadIndicatorInteraction.m; sourceTree = ""; }; 34CCAF361F0C0599004084F4 /* AppUpdateNag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppUpdateNag.h; sourceTree = ""; }; 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppUpdateNag.m; sourceTree = ""; }; 34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = ""; }; @@ -545,8 +553,6 @@ 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIPage.m; sourceTree = ""; }; 34E8BF361EE9E2FD00F5F4CA /* FingerprintViewScanController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FingerprintViewScanController.h; sourceTree = ""; }; 34E8BF371EE9E2FD00F5F4CA /* FingerprintViewScanController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FingerprintViewScanController.m; sourceTree = ""; }; - 34F3089A1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSUnreadIndicatorInteraction.h; sourceTree = ""; }; - 34F3089B1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSUnreadIndicatorInteraction.m; sourceTree = ""; }; 34F3089D1ECA580B00BB7697 /* OWSUnreadIndicatorCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = OWSUnreadIndicatorCell.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 34F3089E1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = OWSUnreadIndicatorCell.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 34F308A01ECB469700BB7697 /* OWSBezierPathView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBezierPathView.h; sourceTree = ""; }; @@ -1212,6 +1218,8 @@ 45C681B61D305A580050903A /* OWSCall.m */, 45855F351D9498A40084F340 /* OWSContactAvatarBuilder.h */, 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */, + 34C42D621F4734ED0072EC04 /* OWSContactOffersInteraction.h */, + 34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */, 458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */, 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */, 45666EC71D994C0D008FE134 /* OWSGroupAvatarBuilder.h */, @@ -1219,6 +1227,8 @@ 453D28B81D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.h */, 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */, B62D53F41A23CC8B009AAF82 /* TSMessageAdapters */, + 34C42D641F4734ED0072EC04 /* TSUnreadIndicatorInteraction.h */, + 34C42D651F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m */, ); path = Models; sourceTree = ""; @@ -1457,6 +1467,8 @@ 3453D8E91EC0D4ED003F9E6F /* OWSAlerts.swift */, 34F308A01ECB469700BB7697 /* OWSBezierPathView.h */, 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */, + 34C42D5F1F4734CA0072EC04 /* OWSContactOffersCell.h */, + 34C42D601F4734CA0072EC04 /* OWSContactOffersCell.m */, 459311FA1D75C948008DD4F0 /* OWSDeviceTableViewCell.h */, 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */, 450873C91D9D86F4006B54F2 /* OWSExpirableMessageView.h */, @@ -1476,8 +1488,6 @@ 34F3089D1ECA580B00BB7697 /* OWSUnreadIndicatorCell.h */, 34F3089E1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m */, 45A6DAD51EBBF85500893231 /* ReminderView.swift */, - 34F3089A1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.h */, - 34F3089B1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m */, ); name = Views; path = views; @@ -2269,6 +2279,7 @@ 45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */, 45666EC61D99483D008FE134 /* OWSAvatarBuilder.m in Sources */, 45E615161E8C590B0018AD52 /* DisplayableTextFilter.swift in Sources */, + 34C42D611F4734CA0072EC04 /* OWSContactOffersCell.m in Sources */, 34B3F88A1E8DF1700035BE1A /* OWSLinkDeviceViewController.m in Sources */, 76EB068618170B34006006FC /* ContactTableViewCell.m in Sources */, 3497DBEF1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m in Sources */, @@ -2339,6 +2350,7 @@ 34B3F8721E8DF1700035BE1A /* AdvancedSettingsTableViewController.m in Sources */, 45F170D61E315310003FC1F2 /* Weak.swift in Sources */, 34B3F8891E8DF1700035BE1A /* OWSConversationSettingsViewController.m in Sources */, + 34C42D671F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m in Sources */, 34B3F87E1E8DF1700035BE1A /* InboxTableViewCell.m in Sources */, 34B3F8731E8DF1700035BE1A /* AttachmentApprovalViewController.swift in Sources */, B6B1013C196D213F007E3930 /* SignalKeyingStorage.m in Sources */, @@ -2349,6 +2361,7 @@ 45666F7E1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m in Sources */, 4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */, 34D99C941F2937CC00D284D6 /* OWSSwiftUtils.swift in Sources */, + 34C42D661F4734ED0072EC04 /* OWSContactOffersInteraction.m in Sources */, 34B3F8941E8DF1710035BE1A /* SignalsViewController.m in Sources */, 34E8BF381EE9E2FD00F5F4CA /* FingerprintViewScanController.m in Sources */, 76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */, @@ -2356,7 +2369,6 @@ 34B3F87D1E8DF1700035BE1A /* FullImageViewController.m in Sources */, 45666F7B1D9C0533008FE134 /* OWSDatabaseMigration.m in Sources */, B90418E6183E9DD40038554A /* DateUtil.m in Sources */, - 34F3089C1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m in Sources */, 459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Signal/src/Models/OWSContactOffersInteraction.h b/Signal/src/Models/OWSContactOffersInteraction.h new file mode 100644 index 000000000..c8c3ba586 --- /dev/null +++ b/Signal/src/Models/OWSContactOffersInteraction.h @@ -0,0 +1,25 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSContactOffersInteraction : TSInteraction + +//@property (atomic, readonly) BOOL hasMoreUnseenMessages; +// +//@property (atomic, readonly) NSUInteger missingUnseenSafetyNumberChangeCount; + +- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithTimestamp:(uint64_t)timestamp + thread:(TSThread *)thread + // hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages + // missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/Models/OWSContactOffersInteraction.m b/Signal/src/Models/OWSContactOffersInteraction.m new file mode 100644 index 000000000..5929c4283 --- /dev/null +++ b/Signal/src/Models/OWSContactOffersInteraction.m @@ -0,0 +1,54 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSContactOffersInteraction.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSContactOffersInteraction () + +//@property (atomic) BOOL hasMoreUnseenMessages; + +@end + +#pragma mark - + +@implementation OWSContactOffersInteraction + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + return [super initWithCoder:coder]; +} + +- (instancetype)initWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread +// hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages +// missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount +{ + self = [super initWithTimestamp:timestamp inThread:thread]; + + if (!self) { + return self; + } + + // _hasMoreUnseenMessages = hasMoreUnseenMessages; + // _missingUnseenSafetyNumberChangeCount = missingUnseenSafetyNumberChangeCount; + + return self; +} + +- (BOOL)shouldUseReceiptDateForSorting +{ + // Use the timestamp, not the "received at" timestamp to sort, + // since we're creating these interactions after the fact and back-dating them. + return NO; +} + +- (BOOL)isDynamicInteraction +{ + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m index 056ffd175..673170490 100644 --- a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m +++ b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m @@ -4,6 +4,8 @@ #import "OWSMessagesBubblesSizeCalculator.h" #import "OWSCall.h" +#import "OWSContactOffersCell.h" +#import "OWSContactOffersInteraction.h" #import "OWSSystemMessageCell.h" #import "OWSUnreadIndicatorCell.h" #import "TSGenericAttachmentAdapter.h" @@ -40,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) OWSSystemMessageCell *referenceSystemMessageCell; @property (nonatomic, readonly) OWSUnreadIndicatorCell *referenceUnreadIndicatorCell; +@property (nonatomic, readonly) OWSContactOffersCell *referenceContactOffersCell; @end @@ -52,6 +55,7 @@ NS_ASSUME_NONNULL_BEGIN if (self = [super init]) { _referenceSystemMessageCell = [OWSSystemMessageCell new]; _referenceUnreadIndicatorCell = [OWSUnreadIndicatorCell new]; + _referenceContactOffersCell = [OWSContactOffersCell new]; } return self; } @@ -88,6 +92,12 @@ NS_ASSUME_NONNULL_BEGIN = (TSUnreadIndicatorInteraction *)((TSMessageAdapter *)messageData).interaction; return [self sizeForUnreadIndicator:interaction cacheKey:cacheKey layout:layout]; } + case OWSContactOffersAdapter: { + id cacheKey = [self cacheKeyForMessageData:messageData]; + OWSContactOffersInteraction *interaction + = (OWSContactOffersInteraction *)((TSMessageAdapter *)messageData).interaction; + return [self sizeForContactOffers:interaction cacheKey:cacheKey layout:layout]; + } case TSIncomingMessageAdapter: case TSOutgoingMessageAdapter: break; @@ -148,6 +158,26 @@ NS_ASSUME_NONNULL_BEGIN return [cachedSize CGSizeValue]; } + CGSize result = [self.referenceContactOffersCell bubbleSizeForInteraction:interaction + collectionViewWidth:layout.collectionView.width]; + + [self.cache setObject:[NSValue valueWithCGSize:result] forKey:cacheKey]; + + return result; +} + +- (CGSize)sizeForContactOffers:(OWSContactOffersInteraction *)interaction + cacheKey:(id)cacheKey + layout:(JSQMessagesCollectionViewFlowLayout *)layout +{ + OWSAssert(interaction); + OWSAssert(cacheKey); + + NSValue *cachedSize = [self.cache objectForKey:cacheKey]; + if (cachedSize != nil) { + return [cachedSize CGSizeValue]; + } + CGSize result = [self.referenceUnreadIndicatorCell bubbleSizeForInteraction:interaction collectionViewWidth:layout.collectionView.width]; diff --git a/Signal/src/Models/TSMessageAdapaters/OWSMessageData.h b/Signal/src/Models/TSMessageAdapaters/OWSMessageData.h index b30dd539e..207c0f0bf 100644 --- a/Signal/src/Models/TSMessageAdapaters/OWSMessageData.h +++ b/Signal/src/Models/TSMessageAdapaters/OWSMessageData.h @@ -16,6 +16,7 @@ typedef NS_ENUM(NSInteger, TSMessageAdapterType) { TSMediaAttachmentAdapter, TSGenericTextMessageAdapter, // Used when message direction is unknown (outgoing or incoming) TSUnreadIndicatorAdapter, + OWSContactOffersAdapter, }; @protocol OWSMessageData diff --git a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m index cf684bb9b..32a87c676 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m @@ -5,6 +5,7 @@ #import "TSMessageAdapter.h" #import "AttachmentSharing.h" #import "OWSCall.h" +#import "OWSContactOffersInteraction.h" #import "Signal-Swift.h" #import "TSAttachmentPointer.h" #import "TSAttachmentStream.h" @@ -226,6 +227,8 @@ NS_ASSUME_NONNULL_BEGIN adapter.messageType = TSInfoMessageAdapter; } else if ([interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]) { adapter.messageType = TSUnreadIndicatorAdapter; + } else if ([interaction isKindOfClass:[OWSContactOffersInteraction class]]) { + adapter.messageType = OWSContactOffersAdapter; } else if ([interaction isKindOfClass:[TSErrorMessage class]]) { TSErrorMessage *errorMessage = (TSErrorMessage *)interaction; adapter.errorMessageType = errorMessage.errorType; diff --git a/Signal/src/views/TSUnreadIndicatorInteraction.h b/Signal/src/Models/TSUnreadIndicatorInteraction.h similarity index 100% rename from Signal/src/views/TSUnreadIndicatorInteraction.h rename to Signal/src/Models/TSUnreadIndicatorInteraction.h diff --git a/Signal/src/views/TSUnreadIndicatorInteraction.m b/Signal/src/Models/TSUnreadIndicatorInteraction.m similarity index 100% rename from Signal/src/views/TSUnreadIndicatorInteraction.m rename to Signal/src/Models/TSUnreadIndicatorInteraction.m diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index e12348046..5693b6982 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -16,6 +16,8 @@ #import "NewGroupViewController.h" #import "OWSAudioAttachmentPlayer.h" #import "OWSCall.h" +#import "OWSContactOffersCell.h" +#import "OWSContactOffersInteraction.h" #import "OWSContactsManager.h" #import "OWSConversationSettingsViewController.h" #import "OWSConversationSettingsViewDelegate.h" @@ -1649,6 +1651,10 @@ typedef enum : NSUInteger { cell = [self loadUnreadIndicatorCell:indexPath interaction:message.interaction]; break; } + case OWSContactOffersAdapter: { + cell = [self loadContactOffersCell:indexPath interaction:message.interaction]; + break; + } default: { OWSFail(@"using default cell constructor for message: %@", message); cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView @@ -1737,6 +1743,23 @@ typedef enum : NSUInteger { return cell; } +- (JSQMessagesCollectionViewCell *)loadContactOffersCell:(NSIndexPath *)indexPath + interaction:(TSInteraction *)interaction +{ + OWSAssert(indexPath); + OWSAssert(interaction); + OWSAssert([interaction isKindOfClass:[OWSContactOffersInteraction class]]); + + OWSContactOffersInteraction *offersInteraction = (OWSContactOffersInteraction *)interaction; + + OWSContactOffersCell *cell = + [self.collectionView dequeueReusableCellWithReuseIdentifier:[OWSContactOffersCell cellReuseIdentifier] + forIndexPath:indexPath]; + [cell configureWithInteraction:offersInteraction]; + + return cell; +} + - (OWSSystemMessageCell *)loadSystemMessageCell:(NSIndexPath *)indexPath interaction:(TSInteraction *)interaction { OWSAssert(indexPath); @@ -1822,6 +1845,7 @@ typedef enum : NSUInteger { id previousMessage = [self messageAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row - 1 inSection:indexPath.section]]; + // TODO: What about the contact offers? if ([previousMessage.interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]) { // Always show timestamp between unread indicator and the following interaction return YES; @@ -2165,6 +2189,9 @@ typedef enum : NSUInteger { case TSUnreadIndicatorAdapter: OWSFail(@"Unexpected tap for system message."); break; + case OWSContactOffersAdapter: + // TODO: + break; default: DDLogDebug(@"Unhandled bubble touch for interaction: %@.", interaction); break; @@ -4198,8 +4225,10 @@ typedef enum : NSUInteger { - (BOOL)shouldShowCellDecorationsAtIndexPath:(NSIndexPath *)indexPath { TSInteraction *interaction = [self interactionAtIndexPath:indexPath]; - + // Show any top/bottom labels for all but the unread indicator + // + // TODO: What about the contact offers? return ![interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]; } diff --git a/Signal/src/util/ThreadUtil.h b/Signal/src/util/ThreadUtil.h index 6b38fc105..355e4338d 100644 --- a/Signal/src/util/ThreadUtil.h +++ b/Signal/src/util/ThreadUtil.h @@ -12,7 +12,6 @@ NS_ASSUME_NONNULL_BEGIN @class TSInteraction; @class YapDatabaseConnection; @class TSThread; -@class TSUnreadIndicatorInteraction; @interface ThreadDynamicInteractions : NSObject diff --git a/Signal/src/util/ThreadUtil.m b/Signal/src/util/ThreadUtil.m index 5cbf2c319..e157da206 100644 --- a/Signal/src/util/ThreadUtil.m +++ b/Signal/src/util/ThreadUtil.m @@ -3,6 +3,7 @@ // #import "ThreadUtil.h" +#import "OWSContactOffersInteraction.h" #import "OWSContactsManager.h" #import "OWSProfileManager.h" #import "Signal-Swift.h" diff --git a/Signal/src/views/OWSContactOffersCell.h b/Signal/src/views/OWSContactOffersCell.h new file mode 100644 index 000000000..c09827c10 --- /dev/null +++ b/Signal/src/views/OWSContactOffersCell.h @@ -0,0 +1,23 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class OWSContactOffersInteraction; + +@interface OWSContactOffersCell : JSQMessagesCollectionViewCell + +@property (nonatomic, nullable, readonly) OWSContactOffersInteraction *interaction; + +- (void)configureWithInteraction:(OWSContactOffersInteraction *)interaction; + +- (CGSize)bubbleSizeForInteraction:(OWSContactOffersInteraction *)interaction + collectionViewWidth:(CGFloat)collectionViewWidth; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/OWSContactOffersCell.m b/Signal/src/views/OWSContactOffersCell.m new file mode 100644 index 000000000..b66bdf7c4 --- /dev/null +++ b/Signal/src/views/OWSContactOffersCell.m @@ -0,0 +1,238 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSContactOffersCell.h" +#import "NSBundle+JSQMessages.h" +#import "OWSContactOffersInteraction.h" +#import "UIColor+OWS.h" +#import "UIFont+OWS.h" +#import "UIView+OWS.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSContactOffersCell () + +@property (nonatomic, nullable) OWSContactOffersInteraction *interaction; + +@property (nonatomic) UIView *bannerView; +@property (nonatomic) UIView *bannerTopHighlightView; +@property (nonatomic) UIView *bannerBottomHighlightView1; +@property (nonatomic) UIView *bannerBottomHighlightView2; +@property (nonatomic) UILabel *titleLabel; +@property (nonatomic) UILabel *subtitleLabel; + +@end + +#pragma mark - + +@implementation OWSContactOffersCell + +// `[UIView init]` invokes `[self initWithFrame:...]`. +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + [self commontInit]; + } + + return self; +} + +- (void)commontInit +{ + OWSAssert(!self.bannerView); + + [self setTranslatesAutoresizingMaskIntoConstraints:NO]; + + self.backgroundColor = [UIColor whiteColor]; + + self.bannerView = [UIView new]; + self.bannerView.backgroundColor = [UIColor colorWithRGBHex:0xf6eee3]; + [self.contentView addSubview:self.bannerView]; + + self.bannerTopHighlightView = [UIView new]; + self.bannerTopHighlightView.backgroundColor = [UIColor colorWithRGBHex:0xf9f3eb]; + [self.bannerView addSubview:self.bannerTopHighlightView]; + + self.bannerBottomHighlightView1 = [UIView new]; + self.bannerBottomHighlightView1.backgroundColor = [UIColor colorWithRGBHex:0xd1c6b8]; + [self.bannerView addSubview:self.bannerBottomHighlightView1]; + + self.bannerBottomHighlightView2 = [UIView new]; + self.bannerBottomHighlightView2.backgroundColor = [UIColor colorWithRGBHex:0xdbcfc0]; + [self.bannerView addSubview:self.bannerBottomHighlightView2]; + + self.titleLabel = [UILabel new]; + self.titleLabel.textColor = [UIColor colorWithRGBHex:0x403e3b]; + self.titleLabel.font = [self titleFont]; + [self.bannerView addSubview:self.titleLabel]; + + self.subtitleLabel = [UILabel new]; + self.subtitleLabel.textColor = [UIColor ows_infoMessageBorderColor]; + self.subtitleLabel.font = [self subtitleFont]; + // The subtitle may wrap to a second line. + self.subtitleLabel.numberOfLines = 0; + self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping; + self.subtitleLabel.textAlignment = NSTextAlignmentCenter; + [self.contentView addSubview:self.subtitleLabel]; +} + ++ (NSString *)cellReuseIdentifier +{ + return NSStringFromClass([self class]); +} + +- (void)configureWithInteraction:(OWSContactOffersInteraction *)interaction; +{ + OWSAssert(interaction); + + _interaction = interaction; + + self.titleLabel.text = [self titleForInteraction:self.interaction]; + self.subtitleLabel.text = [self subtitleForInteraction:self.interaction]; + + self.backgroundColor = [UIColor whiteColor]; + + [self setNeedsLayout]; +} + +- (UIFont *)titleFont +{ + return [UIFont ows_regularFontWithSize:16.f]; +} + +- (UIFont *)subtitleFont +{ + return [UIFont ows_regularFontWithSize:12.f]; +} + +- (NSString *)titleForInteraction:(OWSContactOffersInteraction *)interaction +{ + return NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR", @"Indicator that separates read from unread messages.") + .uppercaseString; +} + +- (NSString *)subtitleForInteraction:(OWSContactOffersInteraction *)interaction +{ + if (!interaction.hasMoreUnseenMessages) { + return nil; + } + NSString *subtitleFormat = (interaction.missingUnseenSafetyNumberChangeCount > 0 + ? NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_FORMAT", + @"Messages that indicates that there are more unseen messages that be revealed by tapping the 'load " + @"earlier messages' button. Embeds {{the name of the 'load earlier messages' button}}") + : NSLocalizedString( + @"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_AND_SAFETY_NUMBER_CHANGES_FORMAT", + @"Messages that indicates that there are more unseen messages including safety number changes that " + @"be revealed by tapping the 'load earlier messages' button. Embeds {{the name of the 'load earlier " + @"messages' button}}.")); + NSString *loadMoreButtonName = [NSBundle jsq_localizedStringForKey:@"load_earlier_messages"]; + return [NSString stringWithFormat:subtitleFormat, loadMoreButtonName]; +} + +- (CGFloat)subtitleHMargin +{ + return 20.f; +} + +- (CGFloat)subtitleVSpacing +{ + return 3.f; +} + +- (CGFloat)titleInnerHMargin +{ + return 10.f; +} + +- (CGFloat)titleVMargin +{ + return 5.5f; +} + +- (CGFloat)topVMargin +{ + return 5.f; +} + +- (CGFloat)bottomVMargin +{ + return 5.f; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + [self.titleLabel sizeToFit]; + + // It's a bit of a hack, but we use a view that extends _outside_ the cell's bounds + // to draw its background, since we want the background to extend to the edges of the + // collection view. + // + // This layout logic assumes that the cell insets are symmetrical and can be deduced + // from the cell frame. + CGRect bannerViewFrame = CGRectMake(-self.left, + round(self.topVMargin), + round(self.width + self.left * 2.f), + round(self.titleLabel.height + self.titleVMargin * 2.f)); + self.bannerView.frame = [self convertRect:bannerViewFrame toView:self.contentView]; + + // The highlights should be 1px (not 1pt), so adapt their thickness to + // the device resolution. + CGFloat kHighlightThickness = 1.f / [UIScreen mainScreen].scale; + self.bannerTopHighlightView.frame = CGRectMake(0, 0, self.bannerView.width, kHighlightThickness); + self.bannerBottomHighlightView1.frame + = CGRectMake(0, self.bannerView.height - kHighlightThickness * 2.f, self.bannerView.width, kHighlightThickness); + self.bannerBottomHighlightView2.frame + = CGRectMake(0, self.bannerView.height - kHighlightThickness * 1.f, self.bannerView.width, kHighlightThickness); + + [self.titleLabel centerOnSuperview]; + + if (self.subtitleLabel.text.length > 0) { + CGSize subtitleSize = [self.subtitleLabel + sizeThatFits:CGSizeMake(self.contentView.width - [self subtitleHMargin] * 2.f, CGFLOAT_MAX)]; + self.subtitleLabel.frame = CGRectMake(round((self.contentView.width - subtitleSize.width) * 0.5f), + round(self.bannerView.bottom + self.subtitleVSpacing), + ceil(subtitleSize.width), + ceil(subtitleSize.height)); + } +} + +- (CGSize)bubbleSizeForInteraction:(OWSContactOffersInteraction *)interaction + collectionViewWidth:(CGFloat)collectionViewWidth +{ + CGSize result = CGSizeMake(collectionViewWidth, 0); + result.height += self.titleVMargin * 2.f; + result.height += self.topVMargin; + result.height += self.bottomVMargin; + + NSString *title = [self titleForInteraction:interaction]; + NSString *subtitle = [self subtitleForInteraction:interaction]; + + self.titleLabel.text = title; + result.height += ceil([self.titleLabel sizeThatFits:CGSizeZero].height); + + if (subtitle.length > 0) { + result.height += self.subtitleVSpacing; + + self.subtitleLabel.text = subtitle; + result.height += ceil( + [self.subtitleLabel sizeThatFits:CGSizeMake(collectionViewWidth - self.subtitleHMargin * 2.f, CGFLOAT_MAX)] + .height); + } + + return result; +} + +- (void)prepareForReuse +{ + [super prepareForReuse]; + + self.interaction = nil; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/OWSSystemMessageCell.m b/Signal/src/views/OWSSystemMessageCell.m index a0d778811..a560631da 100644 --- a/Signal/src/views/OWSSystemMessageCell.m +++ b/Signal/src/views/OWSSystemMessageCell.m @@ -6,7 +6,6 @@ #import "Environment.h" #import "NSBundle+JSQMessages.h" #import "OWSContactsManager.h" -#import "TSUnreadIndicatorInteraction.h" #import "UIColor+OWS.h" #import "UIFont+OWS.h" #import "UIView+OWS.h"