diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 905ea05be..f61f642f2 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 3400C7991EAFB772008A8584 /* ThreadViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3400C7981EAFB772008A8584 /* ThreadViewHelper.m */; }; 340CB2241EAC155C0001CAA1 /* ContactsViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 340CB2231EAC155C0001CAA1 /* ContactsViewHelper.m */; }; 340CB2271EAC25820001CAA1 /* UpdateGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340CB2261EAC25820001CAA1 /* UpdateGroupViewController.m */; }; + 341207271EE19F6A00463194 /* OWSSystemMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 341207261EE19F6A00463194 /* OWSSystemMessageCell.m */; }; 341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */; }; 34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; }; 34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; }; @@ -377,6 +378,8 @@ 340CB2231EAC155C0001CAA1 /* ContactsViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactsViewHelper.m; sourceTree = ""; }; 340CB2251EAC25820001CAA1 /* UpdateGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UpdateGroupViewController.h; sourceTree = ""; }; 340CB2261EAC25820001CAA1 /* UpdateGroupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UpdateGroupViewController.m; sourceTree = ""; }; + 341207251EE19F6A00463194 /* OWSSystemMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSystemMessageCell.h; sourceTree = ""; }; + 341207261EE19F6A00463194 /* OWSSystemMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSystemMessageCell.m; sourceTree = ""; }; 341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMediaItem+OWS.h"; sourceTree = ""; }; 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMediaItem+OWS.m"; sourceTree = ""; }; 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = ""; }; @@ -1390,6 +1393,8 @@ 45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */, 34330AA11E79686200DF2FB9 /* OWSProgressView.h */, 34330AA21E79686200DF2FB9 /* OWSProgressView.m */, + 341207251EE19F6A00463194 /* OWSSystemMessageCell.h */, + 341207261EE19F6A00463194 /* OWSSystemMessageCell.m */, 34F3089D1ECA580B00BB7697 /* OWSUnreadIndicatorCell.h */, 34F3089E1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m */, 45A6DAD51EBBF85500893231 /* ReminderView.swift */, @@ -2208,6 +2213,7 @@ FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */, 3400C7991EAFB772008A8584 /* ThreadViewHelper.m in Sources */, 76EB054018170B33006006FC /* AppDelegate.m in Sources */, + 341207271EE19F6A00463194 /* OWSSystemMessageCell.m in Sources */, 341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */, 34B3F89C1E8DF3270035BE1A /* BlockListViewController.m in Sources */, 45F2B1941D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m in Sources */, diff --git a/Signal/Images.xcassets/system_message_security.imageset/Contents.json b/Signal/Images.xcassets/system_message_security.imageset/Contents.json new file mode 100644 index 000000000..99c7b4d5f --- /dev/null +++ b/Signal/Images.xcassets/system_message_security.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "system_message_security@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "system_message_security@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "system_message_security@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/system_message_security.imageset/system_message_security@1x.png b/Signal/Images.xcassets/system_message_security.imageset/system_message_security@1x.png new file mode 100644 index 000000000..efdaa2b0c Binary files /dev/null and b/Signal/Images.xcassets/system_message_security.imageset/system_message_security@1x.png differ diff --git a/Signal/Images.xcassets/system_message_security.imageset/system_message_security@2x.png b/Signal/Images.xcassets/system_message_security.imageset/system_message_security@2x.png new file mode 100644 index 000000000..7b995d812 Binary files /dev/null and b/Signal/Images.xcassets/system_message_security.imageset/system_message_security@2x.png differ diff --git a/Signal/Images.xcassets/system_message_security.imageset/system_message_security@3x.png b/Signal/Images.xcassets/system_message_security.imageset/system_message_security@3x.png new file mode 100644 index 000000000..315db7973 Binary files /dev/null and b/Signal/Images.xcassets/system_message_security.imageset/system_message_security@3x.png differ diff --git a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m index 9d5834802..1ed99b553 100644 --- a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m +++ b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m @@ -5,6 +5,7 @@ #import "OWSMessagesBubblesSizeCalculator.h" #import "OWSCall.h" #import "OWSDisplayedMessageCollectionViewCell.h" +#import "OWSSystemMessageCell.h" #import "OWSUnreadIndicatorCell.h" #import "TSGenericAttachmentAdapter.h" #import "TSMessageAdapter.h" @@ -50,13 +51,15 @@ NS_ASSUME_NONNULL_BEGIN { if ([messageData isKindOfClass:[TSMessageAdapter class]]) { TSMessageAdapter *message = (TSMessageAdapter *)messageData; - if (message.messageType == TSInfoMessageAdapter || message.messageType == TSErrorMessageAdapter) { + if (message.messageType == TSErrorMessageAdapter) { + return [OWSSystemMessageCell cellSizeForInteraction:((TSMessageAdapter *)messageData).interaction + collectionViewWidth:layout.collectionView.bounds.size.width]; + } else if (message.messageType == TSInfoMessageAdapter || message.messageType == TSErrorMessageAdapter) { return [self messageBubbleSizeForInfoMessageData:messageData atIndexPath:indexPath withLayout:layout]; } else if (message.messageType == TSUnreadIndicatorAdapter) { return [OWSUnreadIndicatorCell cellSizeForInteraction:(TSUnreadIndicatorInteraction *)((TSMessageAdapter *)messageData).interaction collectionViewWidth:layout.collectionView.bounds.size.width]; - return [self messageBubbleSizeForInfoMessageData:messageData atIndexPath:indexPath withLayout:layout]; } } diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index a0b3dc272..54d6beb8e 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -30,6 +30,7 @@ #import "OWSMessagesInputToolbar.h" #import "OWSMessagesToolbarContentView.h" #import "OWSOutgoingMessageCollectionViewCell.h" +#import "OWSSystemMessageCell.h" #import "OWSUnreadIndicatorCell.h" #import "PropertyListPreferences.h" #import "Signal-Swift.h" @@ -423,6 +424,9 @@ typedef enum : NSUInteger { [self.collectionView registerNib:[OWSCallCollectionViewCell nib] forCellWithReuseIdentifier:[OWSCallCollectionViewCell cellReuseIdentifier]]; + [self.collectionView registerClass:[OWSSystemMessageCell class] + forCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier]]; + [self.collectionView registerClass:[OWSUnreadIndicatorCell class] forCellWithReuseIdentifier:[OWSUnreadIndicatorCell cellReuseIdentifier]]; @@ -1465,7 +1469,7 @@ typedef enum : NSUInteger { break; } case TSErrorMessageAdapter: { - cell = [self loadErrorMessageCellForMessage:(TSMessageAdapter *)message atIndexPath:indexPath]; + cell = [self loadErrorMessageCell:indexPath interaction:message.interaction]; break; } case TSIncomingMessageAdapter: { @@ -1647,21 +1651,36 @@ typedef enum : NSUInteger { return infoCell; } -- (OWSDisplayedMessageCollectionViewCell *)loadErrorMessageCellForMessage:(TSMessageAdapter *)errorMessage - atIndexPath:(NSIndexPath *)indexPath +- (OWSSystemMessageCell *)loadErrorMessageCell:(NSIndexPath *)indexPath interaction:(TSInteraction *)interaction +// ForMessage:(TSMessageAdapter *)errorMessage +// atIndexPath:(NSIndexPath *)indexPath { - OWSDisplayedMessageCollectionViewCell *errorCell = - [self loadDisplayedMessageCollectionViewCellForIndexPath:indexPath]; - errorCell.textView.text = [errorMessage text]; + OWSAssert(indexPath); + OWSAssert(interaction); + // OWSAssert([interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]); - // Disable text selectability. Specifying this in prepareForReuse/awakeFromNib was not sufficient. - errorCell.textView.userInteractionEnabled = NO; - errorCell.textView.selectable = NO; + // TSUnreadIndicatorInteraction *unreadIndicator = (TSUnreadIndicatorInteraction *)interaction; + + OWSSystemMessageCell *cell = + [self.collectionView dequeueReusableCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier] + forIndexPath:indexPath]; + cell.interaction = interaction; + [cell configure]; - errorCell.messageBubbleContainerView.layer.borderColor = [[UIColor ows_errorMessageBorderColor] CGColor]; - errorCell.headerImageView.image = [UIImage imageNamed:@"error_white"]; + return cell; - return errorCell; + // OWSDisplayedMessageCollectionViewCell *errorCell = + // [self loadDisplayedMessageCollectionViewCellForIndexPath:indexPath]; + // errorCell.textView.text = [errorMessage text]; + // + // // Disable text selectability. Specifying this in prepareForReuse/awakeFromNib was not sufficient. + // errorCell.textView.userInteractionEnabled = NO; + // errorCell.textView.selectable = NO; + // + // errorCell.messageBubbleContainerView.layer.borderColor = [[UIColor ows_errorMessageBorderColor] CGColor]; + // errorCell.headerImageView.image = [UIImage imageNamed:@"error_white"]; + // + // return errorCell; } #pragma mark - Adjusting cell label heights @@ -3908,8 +3927,10 @@ typedef enum : NSUInteger { - (BOOL)isSpecialItemAtIndexPath:(NSIndexPath *)indexPath { + // TODO: TSInteraction *interaction = [self interactionAtIndexPath:indexPath]; - return [interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]; + return ([interaction isKindOfClass:[TSUnreadIndicatorInteraction class]] || + [interaction isKindOfClass:[TSErrorMessage class]]); } #pragma mark - Class methods diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index ff1d2c683..cce5df7fe 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -7,6 +7,11 @@ #import "Signal-Swift.h" #import "ThreadUtil.h" #import +#import +#import +#import +#import +#import #import #import @@ -35,6 +40,10 @@ NS_ASSUME_NONNULL_BEGIN return [OWSTableSection sectionWithTitle:@"Messages" items:@[ + [OWSTableItem itemWithTitle:@"Create all system messages" + actionBlock:^{ + [DebugUIMessages createSystemMessagesInThread:thread]; + }], [OWSTableItem itemWithTitle:@"Send 10 messages (1/sec.)" actionBlock:^{ [DebugUIMessages sendTextMessage:10 thread:thread]; @@ -492,6 +501,147 @@ NS_ASSUME_NONNULL_BEGIN [ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender]; } ++ (OWSSignalServiceProtosEnvelope *)createEnvelopeForThread:(TSThread *)thread +{ + OWSAssert(thread); + + OWSSignalServiceProtosEnvelopeBuilder *builder = [OWSSignalServiceProtosEnvelopeBuilder new]; + + if ([thread isKindOfClass:[TSGroupThread class]]) { + TSGroupThread *gThread = (TSGroupThread *)thread; + [builder setSource:gThread.groupModel.groupMemberIds[0]]; + } else if ([thread isKindOfClass:[TSContactThread class]]) { + TSContactThread *contactThread = (TSContactThread *)thread; + [builder setSource:contactThread.contactIdentifier]; + } + + return [builder build]; +} + ++ (void)createSystemMessagesInThread:(TSThread *)thread +{ + OWSAssert(thread); + + [[TSStorageManager sharedManager].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + + if ([thread isKindOfClass:[TSContactThread class]]) { + TSContactThread *contactThread = (TSContactThread *)thread; + + [[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + withCallNumber:@"+19174054215" + callType:RPRecentCallTypeIncoming + inThread:contactThread] saveWithTransaction:transaction]; + [[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + withCallNumber:@"+19174054215" + callType:RPRecentCallTypeOutgoing + inThread:contactThread] saveWithTransaction:transaction]; + [[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + withCallNumber:@"+19174054215" + callType:RPRecentCallTypeMissed + inThread:contactThread] saveWithTransaction:transaction]; + [[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + withCallNumber:@"+19174054215" + callType:RPRecentCallTypeOutgoingIncomplete + inThread:contactThread] saveWithTransaction:transaction]; + [[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + withCallNumber:@"+19174054215" + callType:RPRecentCallTypeIncomingIncomplete + inThread:contactThread] saveWithTransaction:transaction]; + } + + { + NSNumber *durationSeconds = [OWSDisappearingMessagesConfiguration validDurationsSeconds][0]; + OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration = + [[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId + enabled:YES + durationSeconds:(uint32_t)[durationSeconds intValue]]; + [[[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + thread:thread + configuration:disappearingMessagesConfiguration + createdByRemoteName:@"Alice"] + saveWithTransaction:transaction]; + } + { + NSNumber *durationSeconds = [[OWSDisappearingMessagesConfiguration validDurationsSeconds] lastObject]; + OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration = + [[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId + enabled:YES + durationSeconds:(uint32_t)[durationSeconds intValue]]; + [[[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + thread:thread + configuration:disappearingMessagesConfiguration + createdByRemoteName:@"Alice"] + saveWithTransaction:transaction]; + } + { + OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration = + [[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId + enabled:NO + durationSeconds:0]; + [[[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + thread:thread + configuration:disappearingMessagesConfiguration + createdByRemoteName:@"Alice"] + saveWithTransaction:transaction]; + } + + [[TSInfoMessage userNotRegisteredMessageInThread:thread] saveWithTransaction:transaction]; + + [[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + messageType:TSInfoMessageTypeSessionDidEnd] saveWithTransaction:transaction]; + // TODO: customMessage? + [[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + messageType:TSInfoMessageTypeGroupUpdate] saveWithTransaction:transaction]; + // TODO: customMessage? + [[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + messageType:TSInfoMessageTypeGroupQuit] saveWithTransaction:transaction]; + + [[TSErrorMessage missingSessionWithEnvelope:[self createEnvelopeForThread:thread] withTransaction:transaction] + saveWithTransaction:transaction]; + [[TSErrorMessage invalidKeyExceptionWithEnvelope:[self createEnvelopeForThread:thread] + withTransaction:transaction] saveWithTransaction:transaction]; + [[TSErrorMessage invalidVersionWithEnvelope:[self createEnvelopeForThread:thread] withTransaction:transaction] + saveWithTransaction:transaction]; + [[TSInvalidIdentityKeyReceivingErrorMessage untrustedKeyWithEnvelope:[self createEnvelopeForThread:thread] + withTransaction:transaction] + saveWithTransaction:transaction]; + [[TSErrorMessage corruptedMessageWithEnvelope:[self createEnvelopeForThread:thread] withTransaction:transaction] + saveWithTransaction:transaction]; + + [[[TSErrorMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + failedMessageType:TSErrorMessageNonBlockingIdentityChange + recipientId:@"+19174054215"] saveWithTransaction:transaction]; + + }]; + + { + OWSDisappearingMessagesConfiguration *configuration = + [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId]; + TSOutgoingMessage *outgoingMessage = + [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + messageBody:@"hi" + attachmentIds:[NSMutableArray new] + expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)]; + PreKeyBundle *preKeyBundle = [[PreKeyBundle alloc] initWithRegistrationId:0 + deviceId:0 + preKeyId:0 + preKeyPublic:[self createRandomNSDataOfSize:16] + signedPreKeyPublic:[self createRandomNSDataOfSize:16] + signedPreKeyId:0 + signedPreKeySignature:[self createRandomNSDataOfSize:16] + identityKey:[self createRandomNSDataOfSize:16]]; + [[TSInvalidIdentityKeySendingErrorMessage untrustedKeyWithOutgoingMessage:outgoingMessage + inThread:thread + forRecipient:@"+19174054215" + preKeyBundle:preKeyBundle] save]; + } +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m index 00446267b..013f09cb6 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m @@ -28,15 +28,38 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Factory Methods +- (void)pushPageWithSection:(OWSTableSection *)section +{ + DebugUITableViewController *viewController = [DebugUITableViewController new]; + OWSTableContents *contents = [OWSTableContents new]; + contents.title = section.headerTitle; + [contents addSection:section]; + viewController.contents = contents; + [self.navigationController pushViewController:viewController animated:YES]; +} + + (void)presentDebugUIForThread:(TSThread *)thread fromViewController:(UIViewController *)fromViewController { OWSAssert(thread); OWSAssert(fromViewController); + DebugUITableViewController *viewController = [DebugUITableViewController new]; + __weak DebugUITableViewController *weakSelf = viewController; + OWSTableContents *contents = [OWSTableContents new]; contents.title = @"Debug: Conversation"; - [contents addSection:[DebugUIMessages sectionForThread:thread]]; + [contents + addSection:[OWSTableSection + sectionWithTitle:[DebugUIMessages sectionForThread:thread].headerTitle + items:@[ + [OWSTableItem + disclosureItemWithText:[DebugUIMessages sectionForThread:thread].headerTitle + actionBlock:^{ + [weakSelf pushPageWithSection:[DebugUIMessages + sectionForThread:thread]]; + }], + ]]]; [contents addSection:[OWSTableSection @@ -129,7 +152,6 @@ NS_ASSUME_NONNULL_BEGIN }], ]]]; - DebugUITableViewController *viewController = [DebugUITableViewController new]; viewController.contents = contents; [viewController presentFromViewController:fromViewController]; } diff --git a/Signal/src/environment/NotificationsManager.m b/Signal/src/environment/NotificationsManager.m index dc7bb85bb..2e72eadbc 100644 --- a/Signal/src/environment/NotificationsManager.m +++ b/Signal/src/environment/NotificationsManager.m @@ -153,6 +153,13 @@ #pragma mark - Signal Messages - (void)notifyUserForErrorMessage:(TSErrorMessage *)message inThread:(TSThread *)thread { + OWSAssert(message); + OWSAssert(thread); + + if (thread.isMuted) { + return; + } + NSString *messageDescription = message.description; if (([UIApplication sharedApplication].applicationState != UIApplicationStateActive) && messageDescription) { @@ -186,6 +193,10 @@ inThread:(TSThread *)thread contactsManager:(id)contactsManager { + OWSAssert(message); + OWSAssert(thread); + OWSAssert(contactsManager); + if (thread.isMuted) { return; } diff --git a/Signal/src/views/OWSSystemMessageCell.h b/Signal/src/views/OWSSystemMessageCell.h new file mode 100644 index 000000000..537bd93a3 --- /dev/null +++ b/Signal/src/views/OWSSystemMessageCell.h @@ -0,0 +1,18 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import + +@class TSInteraction; + +@interface OWSSystemMessageCell : JSQMessagesCollectionViewCell + +@property (nonatomic) TSInteraction *interaction; + +- (void)configure; + ++ (CGSize)cellSizeForInteraction:(TSInteraction *)interaction collectionViewWidth:(CGFloat)collectionViewWidth; + +@end diff --git a/Signal/src/views/OWSSystemMessageCell.m b/Signal/src/views/OWSSystemMessageCell.m new file mode 100644 index 000000000..5163aca2a --- /dev/null +++ b/Signal/src/views/OWSSystemMessageCell.m @@ -0,0 +1,292 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSSystemMessageCell.h" +#import "NSBundle+JSQMessages.h" +#import "TSUnreadIndicatorInteraction.h" +#import "UIColor+OWS.h" +#import "UIFont+OWS.h" +#import "UIView+OWS.h" +#import +#import + +@interface OWSSystemMessageCell () + +//@property (nonatomic) UIView *bannerView; +//@property (nonatomic) UIView *bannerTopHighlightView; +//@property (nonatomic) UIView *bannerBottomHighlightView1; +//@property (nonatomic) UIView *bannerBottomHighlightView2; +@property (nonatomic) UIImageView *imageView; +@property (nonatomic) UILabel *titleLabel; +//@property (nonatomic) UILabel *subtitleLabel; + +@end + +#pragma mark - + +@implementation OWSSystemMessageCell + ++ (NSString *)cellReuseIdentifier +{ + return NSStringFromClass([self class]); +} + +- (void)configure +{ + self.backgroundColor = [UIColor whiteColor]; + + if (!self.titleLabel) { + // 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.imageView = [UIImageView new]; + [self.contentView addSubview:self.imageView]; + + self.titleLabel = [UILabel new]; + self.titleLabel.textColor = [UIColor colorWithRGBHex:0x403e3b]; + self.titleLabel.font = [OWSSystemMessageCell titleFont]; + self.titleLabel.numberOfLines = 0; + self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; + [self.contentView addSubview:self.titleLabel]; + + // self.subtitleLabel = [UILabel new]; + // self.subtitleLabel.text = [OWSSystemMessageCell subtitleForInteraction:self.interaction]; + // self.subtitleLabel.textColor = [UIColor ows_infoMessageBorderColor]; + // self.subtitleLabel.font = [OWSSystemMessageCell subtitleFont]; + // self.subtitleLabel.numberOfLines = 0; + // self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping; + // self.subtitleLabel.textAlignment = NSTextAlignmentCenter; + // [self.contentView addSubview:self.subtitleLabel]; + } + // [self.imageView addRedBorder]; + // [self addRedBorder]; + + UIColor *contentColor = [UIColor ows_darkGrayColor]; + UIImage *icon = [self iconForInteraction:self.interaction]; + self.imageView.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + // self.imageView.tintColor = [UIColor colorWithRGBHex:0x505050]; + self.imageView.tintColor = contentColor; + // self.imageView.image = [OWSSystemMessageCell iconForInteraction:self.interaction]; + self.titleLabel.text = [OWSSystemMessageCell titleForInteraction:self.interaction]; + self.titleLabel.textColor = contentColor; + + [self setNeedsLayout]; +} + +- (UIImage *)iconForInteraction:(TSInteraction *)interaction +{ + UIImage *result = nil; + if ([interaction isKindOfClass:[TSErrorMessage class]]) { + // TODO: + result = [UIImage imageNamed:@"system_message_security"]; + } else { + OWSFail(@"Unknown interaction type"); + return nil; + } + OWSAssert(result); + return result; + // return NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR", @"Indicator that separates read from unread + // messages.") + // .uppercaseString; +} + ++ (NSString *)titleForInteraction:(TSInteraction *)interaction +{ + if ([interaction isKindOfClass:[TSErrorMessage class]]) { + // TODO: Should we move the copy generation into this view? + return interaction.description; + } else { + OWSFail(@"Unknown interaction type"); + return nil; + } + // return NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR", @"Indicator that separates read from unread + // messages.") + // .uppercaseString; +} + ++ (UIFont *)titleFont +{ + return [UIFont ows_regularFontWithSize:13.f]; +} + +//+ (UIFont *)subtitleFont +//{ +// return [UIFont ows_regularFontWithSize:12.f]; +//} + +//+ (NSString *)subtitleForInteraction:(TSUnreadIndicatorInteraction *)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)hMargin +{ + return 30.f; +} + ++ (CGFloat)topVMargin +{ + return 5.f; +} + ++ (CGFloat)bottomVMargin +{ + return 5.f; +} + ++ (CGFloat)hSpacing +{ + return 10.f; +} + ++ (CGFloat)iconSize +{ + return 30.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(OWSSystemMessageCell.topVMargin), + // round(self.width + self.left * 2.f), + // round(self.titleLabel.height + OWSSystemMessageCell.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]; + + CGFloat maxTitleWidth = (self.contentView.width + - ([OWSSystemMessageCell hMargin] * 2.f + [OWSSystemMessageCell hSpacing] + [OWSSystemMessageCell iconSize])); + CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)]; + // CGFloat contentWidth = ceil([OWSSystemMessageCell iconSize] + + // [OWSSystemMessageCell hSpacing] + + // titleSize.width); + // self.imageView.frame = CGRectMake(round((self.contentView.width - contentWidth) * 0.5f), + self.imageView.frame = CGRectMake(round([OWSSystemMessageCell hMargin]), + round((self.contentView.height - [OWSSystemMessageCell iconSize]) * 0.5f), + [OWSSystemMessageCell iconSize], + [OWSSystemMessageCell iconSize]); + self.titleLabel.frame = CGRectMake(round(self.imageView.right + [OWSSystemMessageCell hSpacing]), + round((self.contentView.height - titleSize.height) * 0.5f), + ceil(titleSize.width + 1.f), + ceil(titleSize.height + 1.f)); + // [self.titleLabel addRedBorder]; + // if (self.subtitleLabel.text.length > 0) { + // CGSize subtitleSize = [self.subtitleLabel + // sizeThatFits:CGSizeMake( + // self.contentView.width - [OWSSystemMessageCell subtitleHMargin] * 2.f, CGFLOAT_MAX)]; + // self.subtitleLabel.frame = CGRectMake(round((self.contentView.width - subtitleSize.width) * 0.5f), + // round(self.bannerView.bottom + OWSSystemMessageCell.subtitleVSpacing), + // ceil(subtitleSize.width), + // ceil(subtitleSize.height)); + // } +} + ++ (CGSize)cellSizeForInteraction:(TSInteraction *)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]; + + // Creating a UILabel to measure the layout is expensive, but it's the only + // reliable way to do it. Unread indicators should be rare, so this is acceptable. + UILabel *label = [UILabel new]; + label.font = [self titleFont]; + label.text = title; + label.numberOfLines = 0; + label.lineBreakMode = NSLineBreakByWordWrapping; + CGFloat maxTitleWidth = (collectionViewWidth - ([self hMargin] * 2.f + [self hSpacing] + [self iconSize])); + CGSize titleSize = [label sizeThatFits:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)]; + CGFloat contentHeight = ceil(MAX([self iconSize], titleSize.height)); + result.height += contentHeight; + + // if (subtitle.length > 0) { + // result.height += self.subtitleVSpacing; + // + // label.font = [self subtitleFont]; + // label.text = subtitle; + // // The subtitle may wrap to a second line. + // label.lineBreakMode = NSLineBreakByWordWrapping; + // label.numberOfLines = 0; + // result.height += ceil( + // [label sizeThatFits:CGSizeMake(collectionViewWidth - self.subtitleHMargin * 2.f, + // CGFLOAT_MAX)].height); + // } + + return result; +} + +@end diff --git a/Signal/src/views/TSUnreadIndicatorInteraction.h b/Signal/src/views/TSUnreadIndicatorInteraction.h index c4a3f94a9..b9d962231 100644 --- a/Signal/src/views/TSUnreadIndicatorInteraction.h +++ b/Signal/src/views/TSUnreadIndicatorInteraction.h @@ -2,11 +2,11 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // -#import "TSMessage.h" +#import NS_ASSUME_NONNULL_BEGIN -@interface TSUnreadIndicatorInteraction : TSMessage +@interface TSUnreadIndicatorInteraction : TSInteraction @property (atomic, readonly) BOOL hasMoreUnseenMessages; diff --git a/Signal/src/views/TSUnreadIndicatorInteraction.m b/Signal/src/views/TSUnreadIndicatorInteraction.m index 1ed1f05bf..a66919210 100644 --- a/Signal/src/views/TSUnreadIndicatorInteraction.m +++ b/Signal/src/views/TSUnreadIndicatorInteraction.m @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN @end +#pragma mark - @implementation TSUnreadIndicatorInteraction @@ -27,12 +28,7 @@ NS_ASSUME_NONNULL_BEGIN hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount { - self = [super initWithTimestamp:timestamp - inThread:thread - messageBody:nil - attachmentIds:@[] - expiresInSeconds:0 - expireStartedAt:0]; + self = [super initWithTimestamp:timestamp inThread:thread]; if (!self) { return self;