From 15074cdb8fba7e323e83801131902956e70612c4 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 6 Jun 2017 09:46:00 -0400 Subject: [PATCH] Clean up system message cells, make them tappable, etc. // FREEBIE --- .../Models/OWSMessagesBubblesSizeCalculator.m | 7 +- Signal/src/UserInterface/Strings.swift | 4 + .../ConversationView/MessagesViewController.m | 131 +++++++++++++++--- Signal/src/views/OWSSystemMessageCell.h | 10 ++ Signal/src/views/OWSSystemMessageCell.m | 64 +++++++-- Signal/src/views/OWSUnreadIndicatorCell.h | 1 - Signal/src/views/OWSUnreadIndicatorCell.m | 91 +++++++----- .../translations/en.lproj/Localizable.strings | 11 +- 8 files changed, 250 insertions(+), 69 deletions(-) diff --git a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m index c0e31b45e..e6840bd3f 100644 --- a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m +++ b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m @@ -67,14 +67,19 @@ NS_ASSUME_NONNULL_BEGIN = (TSUnreadIndicatorInteraction *)((TSMessageAdapter *)messageData).interaction; return [self sizeForUnreadIndicator:interaction cacheKey:cacheKey layout:layout]; } + case TSIncomingMessageAdapter: + case TSOutgoingMessageAdapter: + break; default: - // TODO: we need to examine the other cases. + OWSFail(@"---- Unknown sizing interaction: %@", [((TSMessageAdapter *)messageData).interaction class]); break; } } else if ([messageData isKindOfClass:[OWSCall class]]) { id cacheKey = [self cacheKeyForMessageData:messageData]; TSInteraction *interaction = ((OWSCall *)messageData).interaction; return [self sizeForSystemMessage:interaction cacheKey:cacheKey layout:layout]; + } else { + OWSFail(@"Can't size unknown message data type: %@", [messageData class]); } // BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 diff --git a/Signal/src/UserInterface/Strings.swift b/Signal/src/UserInterface/Strings.swift index 36af12bdf..60ef70c61 100644 --- a/Signal/src/UserInterface/Strings.swift +++ b/Signal/src/UserInterface/Strings.swift @@ -27,4 +27,8 @@ import Foundation static let rejectedCallWithUnseenIdentityChangeNotificationBody = NSLocalizedString("MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY", comment: "notification action") static let rejectedCallWithUnseenIdentityChangeNotificationBodyWithoutCallerName = NSLocalizedString("MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY_WITHOUT_CALLER_NAME", comment: "notification action") static let rejectedCallWithUnseenIdentityChangeNotificationBodyWithCallerName = NSLocalizedString("MISSED_CALL_WITH_UNSEEN_IDENTITY_BODY_WITH_CALLER_NAME", comment: "notification action") + + static let callBackAlertTitle = NSLocalizedString("CALL_USER_ALERT_TITLE", comment: "Title for alert offering to call a user.") + static let callBackAlertMessageFormat = NSLocalizedString("CALL_USER_ALERT_MESSAGE_FORMAT", comment: "Message format for alert offering to call a user. Embeds {{the user's display name or phone number}}.") + static let callBackAlertCallButton = NSLocalizedString("CALL_USER_ALERT_CALL_BUTTON", comment: "Label for call button for alert offering to call a user.") } diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index 69a7b54dd..68b38148a 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -151,6 +151,7 @@ typedef enum : NSUInteger { JSQMessagesComposerTextViewPasteDelegate, OWSConversationSettingsViewDelegate, OWSMessagesCollectionViewFlowLayoutDelegate, + OWSSystemMessageCellDelegate, OWSTextViewPasteDelegate, OWSVoiceMemoGestureDelegate, UIDocumentMenuDelegate, @@ -1578,6 +1579,7 @@ typedef enum : NSUInteger { [self.collectionView dequeueReusableCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier] forIndexPath:indexPath]; [cell configureWithInteraction:interaction]; + cell.systemMessageCellDelegate = self; return cell; } @@ -1795,7 +1797,6 @@ typedef enum : NSUInteger { [self showConversationSettings]; } - - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath { @@ -1968,13 +1969,10 @@ typedef enum : NSUInteger { } } break; case TSErrorMessageAdapter: - [self handleErrorMessageTap:(TSErrorMessage *)interaction]; - break; case TSInfoMessageAdapter: - [self handleInfoMessageTap:(TSInfoMessage *)interaction]; - break; case TSCallAdapter: case TSUnreadIndicatorAdapter: + OWSFail(@"Unexpected tap for system message."); break; default: DDLogDebug(@"Unhandled bubble touch for interaction: %@.", interaction); @@ -2218,17 +2216,38 @@ typedef enum : NSUInteger { - (void)handleErrorMessageTap:(TSErrorMessage *)message { - if ([message isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) { - [self tappedInvalidIdentityKeyErrorMessage:(TSInvalidIdentityKeyErrorMessage *)message]; - } else if ([message isKindOfClass:[OWSUnknownContactBlockOfferMessage class]]) { - [self tappedUnknownContactBlockOfferMessage:(OWSUnknownContactBlockOfferMessage *)message]; - } else if (message.errorType == TSErrorMessageInvalidMessage) { - [self tappedCorruptedMessage:message]; - } else if (message.errorType == TSErrorMessageNonBlockingIdentityChange) { - [self tappedNonBlockingIdentityChangeForRecipientId:message.recipientId]; - } else { - DDLogWarn(@"%@ Unhandled tap for error message:%@", self.tag, message); + OWSAssert(message); + + switch (message.errorType) { + case TSErrorMessageInvalidKeyException: + break; + case TSErrorMessageNonBlockingIdentityChange: + [self tappedNonBlockingIdentityChangeForRecipientId:message.recipientId]; + return; + case TSErrorMessageWrongTrustedIdentityKey: + OWSAssert([message isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]); + [self tappedInvalidIdentityKeyErrorMessage:(TSInvalidIdentityKeyErrorMessage *)message]; + return; + case TSErrorMessageMissingKeyId: + // Unused. + break; + case TSErrorMessageNoSession: + break; + case TSErrorMessageInvalidMessage: + [self tappedCorruptedMessage:message]; + return; + case TSErrorMessageDuplicateMessage: + // Unused. + break; + case TSErrorMessageInvalidVersion: + break; + case TSErrorMessageUnknownContactBlockOffer: + OWSAssert([message isKindOfClass:[OWSUnknownContactBlockOfferMessage class]]); + [self tappedUnknownContactBlockOfferMessage:(OWSUnknownContactBlockOfferMessage *)message]; + return; } + + DDLogWarn(@"%@ Unhandled tap for error message:%@", self.tag, message); } - (void)tappedNonBlockingIdentityChangeForRecipientId:(NSString *)signalId @@ -2251,16 +2270,35 @@ typedef enum : NSUInteger { - (void)handleInfoMessageTap:(TSInfoMessage *)message { - if ([message isKindOfClass:[OWSAddToContactsOfferMessage class]]) { - [self tappedAddToContactsOfferMessage:(OWSAddToContactsOfferMessage *)message]; - } else { - DDLogInfo(@"%@ Unhandled tap for info message:%@", self.tag, message); + OWSAssert(message); + + switch (message.messageType) { + case TSInfoMessageUserNotRegistered: + break; + case TSInfoMessageTypeSessionDidEnd: + break; + case TSInfoMessageTypeUnsupportedMessage: + // Unused. + break; + case TSInfoMessageAddToContactsOffer: + OWSAssert([message isKindOfClass:[OWSAddToContactsOfferMessage class]]); + [self tappedAddToContactsOfferMessage:(OWSAddToContactsOfferMessage *)message]; + return; + case TSInfoMessageTypeGroupUpdate: + [self showConversationSettings]; + return; + case TSInfoMessageTypeGroupQuit: + break; + case TSInfoMessageTypeDisappearingMessagesUpdate: + [self showConversationSettings]; + return; } + + DDLogInfo(@"%@ Unhandled tap for info message:%@", self.tag, message); } - (void)tappedCorruptedMessage:(TSErrorMessage *)message { - NSString *alertMessage = [NSString stringWithFormat:NSLocalizedString(@"CORRUPTED_SESSION_DESCRIPTION", @"ActionSheet title"), self.thread.name]; @@ -2394,6 +2432,59 @@ typedef enum : NSUInteger { editImmediately:YES]; } +- (void)handleCallTap:(TSCall *)call +{ + OWSAssert(call); + + if (![self.thread isKindOfClass:[TSContactThread class]]) { + DDLogError(@"%@ unexpected thread: %@ in %s", self.tag, self.thread, __PRETTY_FUNCTION__); + OWSAssert(NO); + return; + } + + TSContactThread *contactThread = (TSContactThread *)self.thread; + NSString *displayName = [self.contactsManager displayNameForPhoneIdentifier:contactThread.contactIdentifier]; + + UIAlertController *alertController = [UIAlertController + alertControllerWithTitle:[CallStrings callBackAlertTitle] + message:[NSString stringWithFormat:[CallStrings callBackAlertMessageFormat], displayName] + preferredStyle:UIAlertControllerStyleAlert]; + + __weak MessagesViewController *weakSelf = self; + UIAlertAction *callAction = [UIAlertAction actionWithTitle:[CallStrings callBackAlertCallButton] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + [weakSelf callAction:nil]; + }]; + [alertController addAction:callAction]; + UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil) + style:UIAlertActionStyleCancel + handler:nil]; + [alertController addAction:dismissAction]; + + [[UIApplication sharedApplication].frontmostViewController presentViewController:alertController + animated:YES + completion:nil]; +} + +#pragma mark - OWSSystemMessageCellDelegate + +- (void)didTapSystemMessageWithInteraction:(TSInteraction *)interaction +{ + OWSAssert([NSThread isMainThread]); + OWSAssert(interaction); + + if ([interaction isKindOfClass:[TSErrorMessage class]]) { + [self handleErrorMessageTap:(TSErrorMessage *)interaction]; + } else if ([interaction isKindOfClass:[TSInfoMessage class]]) { + [self handleInfoMessageTap:(TSInfoMessage *)interaction]; + } else if ([interaction isKindOfClass:[TSCall class]]) { + [self handleCallTap:(TSCall *)interaction]; + } else { + OWSFail(@"Tap for system messages of unknown type: %@", [interaction class]); + } +} + #pragma mark - ContactEditingDelegate - (void)didFinishEditingContact diff --git a/Signal/src/views/OWSSystemMessageCell.h b/Signal/src/views/OWSSystemMessageCell.h index 5013f5056..57e564aa9 100644 --- a/Signal/src/views/OWSSystemMessageCell.h +++ b/Signal/src/views/OWSSystemMessageCell.h @@ -7,8 +7,18 @@ @class TSInteraction; +@protocol OWSSystemMessageCellDelegate + +- (void)didTapSystemMessageWithInteraction:(TSInteraction *)interaction; + +@end + +#pragma mark - + @interface OWSSystemMessageCell : JSQMessagesCollectionViewCell +@property (nonatomic, weak) id systemMessageCellDelegate; + @property (nonatomic, nullable, readonly) TSInteraction *interaction; - (void)configureWithInteraction:(TSInteraction *)interaction; diff --git a/Signal/src/views/OWSSystemMessageCell.m b/Signal/src/views/OWSSystemMessageCell.m index 4e556f56a..24f46e4f0 100644 --- a/Signal/src/views/OWSSystemMessageCell.m +++ b/Signal/src/views/OWSSystemMessageCell.m @@ -26,6 +26,47 @@ @implementation OWSSystemMessageCell +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + [self commontInit]; + } + + return self; +} + +- (instancetype)init +{ + if (self = [super init]) { + [self commontInit]; + } + + return self; +} + +- (void)commontInit +{ + OWSAssert(!self.imageView); + + [self setTranslatesAutoresizingMaskIntoConstraints:NO]; + + self.backgroundColor = [UIColor whiteColor]; + + 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]; + + UITapGestureRecognizer *tap = + [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; + [self addGestureRecognizer:tap]; +} + + (NSString *)cellReuseIdentifier { return NSStringFromClass([self class]); @@ -37,20 +78,6 @@ _interaction = interaction; - self.backgroundColor = [UIColor whiteColor]; - - if (!self.titleLabel) { - 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]; - } - UIImage *icon = [self iconForInteraction:self.interaction]; self.imageView.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; self.imageView.tintColor = [self iconColorForInteraction:self.interaction]; @@ -213,4 +240,13 @@ self.interaction = nil; } +#pragma mark - Gesture recognizers + +- (void)handleTapGesture:(UITapGestureRecognizer *)tap +{ + OWSAssert(self.interaction); + + [self.systemMessageCellDelegate didTapSystemMessageWithInteraction:self.interaction]; +} + @end diff --git a/Signal/src/views/OWSUnreadIndicatorCell.h b/Signal/src/views/OWSUnreadIndicatorCell.h index 9e3e8451e..ba085330f 100644 --- a/Signal/src/views/OWSUnreadIndicatorCell.h +++ b/Signal/src/views/OWSUnreadIndicatorCell.h @@ -12,7 +12,6 @@ @property (nonatomic, nullable, readonly) TSUnreadIndicatorInteraction *interaction; - (void)configureWithInteraction:(TSUnreadIndicatorInteraction *)interaction; -; + (CGSize)cellSizeForInteraction:(TSUnreadIndicatorInteraction *)interaction collectionViewWidth:(CGFloat)collectionViewWidth; diff --git a/Signal/src/views/OWSUnreadIndicatorCell.m b/Signal/src/views/OWSUnreadIndicatorCell.m index d46120e86..4662c0c2d 100644 --- a/Signal/src/views/OWSUnreadIndicatorCell.m +++ b/Signal/src/views/OWSUnreadIndicatorCell.m @@ -27,6 +27,64 @@ @implementation OWSUnreadIndicatorCell +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + [self commontInit]; + } + + return self; +} + +- (instancetype)init +{ + if (self = [super init]) { + [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.text = [OWSUnreadIndicatorCell titleForInteraction:self.interaction]; + self.titleLabel.textColor = [UIColor colorWithRGBHex:0x403e3b]; + self.titleLabel.font = [OWSUnreadIndicatorCell titleFont]; + [self.bannerView addSubview:self.titleLabel]; + + self.subtitleLabel = [UILabel new]; + self.subtitleLabel.text = [OWSUnreadIndicatorCell subtitleForInteraction:self.interaction]; + self.subtitleLabel.textColor = [UIColor ows_infoMessageBorderColor]; + self.subtitleLabel.font = [OWSUnreadIndicatorCell subtitleFont]; + self.subtitleLabel.numberOfLines = 0; + self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping; + self.subtitleLabel.textAlignment = NSTextAlignmentCenter; + [self.contentView addSubview:self.subtitleLabel]; +} + + (NSString *)cellReuseIdentifier { return NSStringFromClass([self class]); @@ -40,38 +98,7 @@ 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.titleLabel = [UILabel new]; - self.titleLabel.text = [OWSUnreadIndicatorCell titleForInteraction:self.interaction]; - self.titleLabel.textColor = [UIColor colorWithRGBHex:0x403e3b]; - self.titleLabel.font = [OWSUnreadIndicatorCell titleFont]; - [self.bannerView addSubview:self.titleLabel]; - - self.subtitleLabel = [UILabel new]; - self.subtitleLabel.text = [OWSUnreadIndicatorCell subtitleForInteraction:self.interaction]; - self.subtitleLabel.textColor = [UIColor ows_infoMessageBorderColor]; - self.subtitleLabel.font = [OWSUnreadIndicatorCell subtitleFont]; - self.subtitleLabel.numberOfLines = 0; - self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping; - self.subtitleLabel.textAlignment = NSTextAlignmentCenter; - [self.contentView addSubview:self.subtitleLabel]; - } + [self setNeedsLayout]; } + (UIFont *)titleFont diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 7ff06eaa7..372da52d2 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -199,6 +199,15 @@ /* embeds {{Call Status}} in call screen label. For ongoing calls, {{Call Status}} is a seconds timer like 01:23, otherwise {{Call Status}} is a short text like 'Ringing', 'Busy', or 'Failed Call' */ "CALL_STATUS_FORMAT" = "Signal %@"; +/* Label for call button for alert offering to call a user. */ +"CALL_USER_ALERT_CALL_BUTTON" = "Call"; + +/* Message format for alert offering to call a user. Embeds {{the user's display name or phone number}}. */ +"CALL_USER_ALERT_MESSAGE_FORMAT" = "Would you like to call %@?"; + +/* Title for alert offering to call a user. */ +"CALL_USER_ALERT_TITLE" = "Call?"; + /* Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy. */ "CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL" = "You can answer calls directly from your lock screen and see the name and phone number for incoming calls if you change your settings.\n\nSee the privacy settings for details."; @@ -1063,7 +1072,7 @@ /* button title to confirm adding a recipient to a group when their safety number has recently changed */ "SAFETY_NUMBER_CHANGED_CONFIRM_ADD_TO_GROUP_ACTION" = "Confirm and Add to Group"; -/* button title to confirm calling a recipient whose safety number recently changed */ +/* alert button text to confirm placing an outgoing call after the recipients Safety Number has changed. */ "SAFETY_NUMBER_CHANGED_CONFIRM_CALL_ACTION" = "Confirm and Call"; /* button title to confirm sending to a recipient whose safety number recently changed */