diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 7dbd0234a..a403a3ff3 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -43,7 +43,6 @@ 340FC8D0205BF2FA007AEB0F /* OWSBackupIO.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8CE205BF2FA007AEB0F /* OWSBackupIO.m */; }; 341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */ = {isa = PBXBuildFile; fileRef = 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */; }; 34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */; }; - 3427C64020EFD43E00EEC730 /* OWSCallMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C63E20EFD43D00EEC730 /* OWSCallMessageCell.m */; }; 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430FE171F7751D4000EC51B /* GiphyAPI.swift */; }; 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 */; }; @@ -642,8 +641,6 @@ 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIMisc.m; sourceTree = ""; }; 34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQuotedMessageView.m; sourceTree = ""; }; 34277A5D20751BDC006049F2 /* OWSQuotedMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQuotedMessageView.h; sourceTree = ""; }; - 3427C63E20EFD43D00EEC730 /* OWSCallMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSCallMessageCell.m; sourceTree = ""; }; - 3427C63F20EFD43E00EEC730 /* OWSCallMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSCallMessageCell.h; sourceTree = ""; }; 3430FE171F7751D4000EC51B /* GiphyAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyAPI.swift; sourceTree = ""; }; 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = ""; }; 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "dripicons-v2.ttf"; sourceTree = ""; }; @@ -1758,8 +1755,6 @@ 34DBF006206C3CB200025978 /* OWSBubbleShapeView.m */, 34DBF002206BD5A500025978 /* OWSBubbleView.h */, 34DBF001206BD5A500025978 /* OWSBubbleView.m */, - 3427C63F20EFD43E00EEC730 /* OWSCallMessageCell.h */, - 3427C63E20EFD43D00EEC730 /* OWSCallMessageCell.m */, 34D1F09A1F867BFC0066283D /* OWSContactOffersCell.h */, 34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */, 3403B95C20EA9527001A1F44 /* OWSContactShareButtonsView.h */, @@ -3209,7 +3204,6 @@ 34DBF007206C3CB200025978 /* OWSBubbleShapeView.m in Sources */, 34D1F0BA1F8800D90066283D /* OWSAudioMessageView.m in Sources */, 34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */, - 3427C64020EFD43E00EEC730 /* OWSCallMessageCell.m in Sources */, 45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */, 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, 34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */, diff --git a/Signal/Images.xcassets/phone-down.imageset/Contents.json b/Signal/Images.xcassets/phone-down.imageset/Contents.json deleted file mode 100644 index 9f4a311e4..000000000 --- a/Signal/Images.xcassets/phone-down.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "phonedown-20@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "phonedown-20@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "phonedown-20@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Signal/Images.xcassets/phone-down.imageset/phonedown-20@1x.png b/Signal/Images.xcassets/phone-down.imageset/phonedown-20@1x.png deleted file mode 100644 index fb2b5a1b7..000000000 Binary files a/Signal/Images.xcassets/phone-down.imageset/phonedown-20@1x.png and /dev/null differ diff --git a/Signal/Images.xcassets/phone-down.imageset/phonedown-20@2x.png b/Signal/Images.xcassets/phone-down.imageset/phonedown-20@2x.png deleted file mode 100644 index 8d69dc0cb..000000000 Binary files a/Signal/Images.xcassets/phone-down.imageset/phonedown-20@2x.png and /dev/null differ diff --git a/Signal/Images.xcassets/phone-down.imageset/phonedown-20@3x.png b/Signal/Images.xcassets/phone-down.imageset/phonedown-20@3x.png deleted file mode 100644 index badca9731..000000000 Binary files a/Signal/Images.xcassets/phone-down.imageset/phonedown-20@3x.png and /dev/null differ diff --git a/Signal/Images.xcassets/phone-up.imageset/Contents.json b/Signal/Images.xcassets/phone-up.imageset/Contents.json deleted file mode 100644 index 80c9d1362..000000000 --- a/Signal/Images.xcassets/phone-up.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "phoneup-20@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "phoneup-20@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "phoneup-20@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Signal/Images.xcassets/phone-up.imageset/phoneup-20@1x.png b/Signal/Images.xcassets/phone-up.imageset/phoneup-20@1x.png deleted file mode 100644 index cf8e87311..000000000 Binary files a/Signal/Images.xcassets/phone-up.imageset/phoneup-20@1x.png and /dev/null differ diff --git a/Signal/Images.xcassets/phone-up.imageset/phoneup-20@2x.png b/Signal/Images.xcassets/phone-up.imageset/phoneup-20@2x.png deleted file mode 100644 index 3419e69be..000000000 Binary files a/Signal/Images.xcassets/phone-up.imageset/phoneup-20@2x.png and /dev/null differ diff --git a/Signal/Images.xcassets/phone-up.imageset/phoneup-20@3x.png b/Signal/Images.xcassets/phone-up.imageset/phoneup-20@3x.png deleted file mode 100644 index 1c9ec9d2b..000000000 Binary files a/Signal/Images.xcassets/phone-up.imageset/phoneup-20@3x.png and /dev/null differ diff --git a/Signal/Images.xcassets/system_message_group.imageset/Contents.json b/Signal/Images.xcassets/system_message_group.imageset/Contents.json deleted file mode 100644 index b08517114..000000000 --- a/Signal/Images.xcassets/system_message_group.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "system_message_group@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "system_message_group@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "system_message_group@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Signal/Images.xcassets/system_message_group.imageset/system_message_group@1x.png b/Signal/Images.xcassets/system_message_group.imageset/system_message_group@1x.png deleted file mode 100644 index 5ed663893..000000000 Binary files a/Signal/Images.xcassets/system_message_group.imageset/system_message_group@1x.png and /dev/null differ diff --git a/Signal/Images.xcassets/system_message_group.imageset/system_message_group@2x.png b/Signal/Images.xcassets/system_message_group.imageset/system_message_group@2x.png deleted file mode 100644 index 58ddd6d2b..000000000 Binary files a/Signal/Images.xcassets/system_message_group.imageset/system_message_group@2x.png and /dev/null differ diff --git a/Signal/Images.xcassets/system_message_group.imageset/system_message_group@3x.png b/Signal/Images.xcassets/system_message_group.imageset/system_message_group@3x.png deleted file mode 100644 index 4e9410665..000000000 Binary files a/Signal/Images.xcassets/system_message_group.imageset/system_message_group@3x.png and /dev/null differ diff --git a/Signal/Images.xcassets/system_message_info.imageset/Contents.json b/Signal/Images.xcassets/system_message_info.imageset/Contents.json deleted file mode 100644 index 17efdbeb5..000000000 --- a/Signal/Images.xcassets/system_message_info.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "system_message_info@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "system_message_info@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "system_message_info@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Signal/Images.xcassets/system_message_info.imageset/system_message_info@1x.png b/Signal/Images.xcassets/system_message_info.imageset/system_message_info@1x.png deleted file mode 100644 index ae5a57b65..000000000 Binary files a/Signal/Images.xcassets/system_message_info.imageset/system_message_info@1x.png and /dev/null differ diff --git a/Signal/Images.xcassets/system_message_info.imageset/system_message_info@2x.png b/Signal/Images.xcassets/system_message_info.imageset/system_message_info@2x.png deleted file mode 100644 index 06cde5bd6..000000000 Binary files a/Signal/Images.xcassets/system_message_info.imageset/system_message_info@2x.png and /dev/null differ diff --git a/Signal/Images.xcassets/system_message_info.imageset/system_message_info@3x.png b/Signal/Images.xcassets/system_message_info.imageset/system_message_info@3x.png deleted file mode 100644 index eef10d744..000000000 Binary files a/Signal/Images.xcassets/system_message_info.imageset/system_message_info@3x.png and /dev/null differ diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 888d9417c..2871f021e 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -40,6 +40,7 @@ #import #import #import +#import #import #import #import @@ -596,6 +597,9 @@ static NSTimeInterval launchStartedAt; // Mark all "attempting out" messages as "unsent", i.e. any messages that were not successfully // sent before the app exited should be marked as failures. [[[OWSFailedMessagesJob alloc] initWithPrimaryStorage:[OWSPrimaryStorage sharedManager]] run]; + // Mark all "incomplete" calls as missed, e.g. any incoming or outgoing calls that were not + // connected, failed or hung up before the app existed should be marked as missed. + [[[OWSIncompleteCallsJob alloc] initWithPrimaryStorage:[OWSPrimaryStorage sharedManager]] run]; [[[OWSFailedAttachmentDownloadsJob alloc] initWithPrimaryStorage:[OWSPrimaryStorage sharedManager]] run]; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h b/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h index 6e8115bf6..d1ccf8da5 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h @@ -12,11 +12,15 @@ NS_ASSUME_NONNULL_BEGIN @class TSAttachmentPointer; @class TSAttachmentStream; @class TSCall; +@class TSErrorMessage; @class TSInteraction; +@class TSInvalidIdentityKeyErrorMessage; +@class TSInvalidIdentityKeyErrorMessage; @class TSMessage; @class TSOutgoingMessage; @class TSQuotedMessage; @class YapDatabaseReadTransaction; +@class YapDatabaseReadTransaction; @protocol ConversationViewCellDelegate @@ -26,14 +30,15 @@ NS_ASSUME_NONNULL_BEGIN - (void)showMetadataViewForViewItem:(ConversationViewItem *)conversationItem; - (void)conversationCell:(ConversationViewCell *)cell didTapReplyForViewItem:(ConversationViewItem *)conversationItem; -#pragma mark - Calls - -- (void)didTapCall:(TSCall *)call; - #pragma mark - System Cell -// TODO: We might want to decompose this method. -- (void)didTapSystemMessageWithInteraction:(TSInteraction *)interaction; +- (void)tappedNonBlockingIdentityChangeForRecipientId:(nullable NSString *)signalId; +- (void)tappedInvalidIdentityKeyErrorMessage:(TSInvalidIdentityKeyErrorMessage *)errorMessage; +- (void)tappedCorruptedMessage:(TSErrorMessage *)message; +- (void)resendGroupUpdateForErrorMessage:(TSErrorMessage *)message; +- (void)showFingerprintWithRecipientId:(NSString *)recipientId; +- (void)showConversationSettings; +- (void)handleCallTap:(TSCall *)call; #pragma mark - Offers diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSCallMessageCell.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSCallMessageCell.h deleted file mode 100644 index 03a53fda5..000000000 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSCallMessageCell.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ConversationViewCell.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSInteraction; - -@interface OWSCallMessageCell : ConversationViewCell - -+ (NSString *)cellReuseIdentifier; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSCallMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSCallMessageCell.m deleted file mode 100644 index d824c6a4b..000000000 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSCallMessageCell.m +++ /dev/null @@ -1,386 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSCallMessageCell.h" -#import "ConversationViewItem.h" -#import "OWSBubbleView.h" -#import "OWSMessageFooterView.h" -#import "Signal-Swift.h" -#import "UIColor+OWS.h" -#import "UIFont+OWS.h" -#import "UIView+OWS.h" -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSCallMessageCell () - -@property (nonatomic, nullable) TSInteraction *interaction; - -@property (nonatomic) OWSBubbleView *bubbleView; -@property (nonatomic) UIImageView *imageView; -@property (nonatomic) UIView *circleView; -@property (nonatomic) UILabel *titleLabel; -@property (nonatomic) OWSMessageFooterView *footerView; -@property (nonatomic) UIStackView *hStackView; -@property (nonatomic) UIStackView *vStackView; -@property (nonatomic) NSMutableArray *layoutConstraints; - -@end - -#pragma mark - - -@implementation OWSCallMessageCell - -// `[UIView init]` invokes `[self initWithFrame:...]`. -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - [self commontInit]; - } - - return self; -} - -- (void)commontInit -{ - OWSAssert(!self.imageView); - - self.layoutMargins = UIEdgeInsetsZero; - self.contentView.layoutMargins = UIEdgeInsetsZero; - - self.layoutConstraints = [NSMutableArray new]; - - self.bubbleView = [OWSBubbleView new]; - self.bubbleView.userInteractionEnabled = NO; - [self.contentView addSubview:self.bubbleView]; - [self.bubbleView autoPinEdgeToSuperviewEdge:ALEdgeTop]; - [self.bubbleView autoPinEdgeToSuperviewEdge:ALEdgeBottom]; - - self.imageView = [UIImageView new]; - [self.imageView setContentHuggingHigh]; - - self.circleView = [UIView new]; - self.circleView.backgroundColor = [UIColor whiteColor]; - self.circleView.layer.cornerRadius = self.circleSize * 0.5f; - [self.circleView addSubview:self.imageView]; - [self.imageView autoCenterInSuperview]; - [self.circleView autoSetDimension:ALDimensionWidth toSize:self.circleSize]; - [self.circleView autoSetDimension:ALDimensionHeight toSize:self.circleSize]; - [self.circleView setContentHuggingHigh]; - - self.titleLabel = [UILabel new]; - self.titleLabel.numberOfLines = 0; - self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; - [self.titleLabel setContentHuggingLow]; - - self.hStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ - self.circleView, - self.titleLabel, - ]]; - self.hStackView.axis = UILayoutConstraintAxisHorizontal; - self.hStackView.spacing = self.hSpacing; - self.hStackView.alignment = UIStackViewAlignmentCenter; - - self.footerView = [OWSMessageFooterView new]; - - self.vStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ - self.hStackView, - self.footerView, - ]]; - self.vStackView.axis = UILayoutConstraintAxisVertical; - self.vStackView.spacing = self.vSpacing; - self.vStackView.userInteractionEnabled = NO; - [self.bubbleView addSubview:self.vStackView]; - [self.vStackView autoPinToSuperviewEdges]; - - UITapGestureRecognizer *tap = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; - [self addGestureRecognizer:tap]; - - UILongPressGestureRecognizer *longPress = - [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)]; - [self addGestureRecognizer:longPress]; -} - -- (void)configureFonts -{ - // Update cell to reflect changes in dynamic text. - self.titleLabel.font = UIFont.ows_dynamicTypeSubheadlineFont; -} - -+ (NSString *)cellReuseIdentifier -{ - return NSStringFromClass([self class]); -} - -- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssert(self.conversationStyle); - OWSAssert(self.viewItem); - OWSAssert([self.viewItem.interaction isKindOfClass:[TSCall class]]); - - TSCall *call = (TSCall *)self.viewItem.interaction; - - self.bubbleView.bubbleColor = [self bubbleColorForCall:call]; - - UIImage *icon = [self iconForCall:call]; - self.imageView.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - self.imageView.tintColor = [self iconColorForCall:call]; - self.titleLabel.textColor = [self textColorForCall:call]; - [self applyTitleForCall:call label:self.titleLabel]; - - if (self.hasFooter) { - [self.footerView configureWithConversationViewItem:self.viewItem - isOverlayingMedia:NO - conversationStyle:self.conversationStyle - isIncoming:call.isIncoming]; - self.footerView.hidden = NO; - } else { - self.footerView.hidden = YES; - } - - if (call.isIncoming) { - [self.layoutConstraints addObjectsFromArray:@[ - [self.bubbleView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:self.conversationStyle.gutterLeading], - [self.bubbleView autoPinEdgeToSuperviewEdge:ALEdgeTrailing - withInset:self.conversationStyle.gutterTrailing - relation:NSLayoutRelationGreaterThanOrEqual], - ]]; - } else { - [self.layoutConstraints addObjectsFromArray:@[ - [self.bubbleView autoPinEdgeToSuperviewEdge:ALEdgeLeading - withInset:self.conversationStyle.gutterLeading - relation:NSLayoutRelationGreaterThanOrEqual], - [self.bubbleView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:self.conversationStyle.gutterTrailing], - ]]; - } - - CGSize cellSize = [self cellSizeWithTransaction:transaction]; - [self.layoutConstraints addObjectsFromArray:[self.bubbleView autoSetDimensionsToSize:cellSize]]; - - self.vStackView.layoutMarginsRelativeArrangement = YES; - self.vStackView.layoutMargins = UIEdgeInsetsMake(self.conversationStyle.textInsetTop, - self.conversationStyle.textInsetHorizontal, - self.conversationStyle.textInsetBottom, - self.conversationStyle.textInsetHorizontal); -} - -- (BOOL)hasFooter -{ - return !self.viewItem.shouldHideFooter; -} - -- (CGFloat)circleSize -{ - return 48.f; -} - -- (UIColor *)textColorForCall:(TSCall *)call -{ - return [self.conversationStyle bubbleTextColorWithCall:call]; -} - -- (UIColor *)bubbleColorForCall:(TSCall *)call -{ - return [self.conversationStyle bubbleColorWithCall:call]; -} - -- (UIColor *)iconColorForCall:(TSCall *)call -{ - switch (call.callType) { - case RPRecentCallTypeIncoming: - case RPRecentCallTypeOutgoing: - case RPRecentCallTypeIncomingIncomplete: - case RPRecentCallTypeOutgoingIncomplete: - return [UIColor ows_greenColor]; - case RPRecentCallTypeIncomingMissed: - case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: - case RPRecentCallTypeIncomingDeclined: - return [UIColor ows_redColor]; - } -} - -- (UIImage *)iconForCall:(TSCall *)call -{ - UIImage *result = nil; - switch (call.callType) { - case RPRecentCallTypeIncoming: - case RPRecentCallTypeOutgoing: - case RPRecentCallTypeIncomingIncomplete: - case RPRecentCallTypeOutgoingIncomplete: - result = [UIImage imageNamed:@"phone-up"]; - break; - case RPRecentCallTypeIncomingMissed: - case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: - case RPRecentCallTypeIncomingDeclined: - result = [UIImage imageNamed:@"phone-down"]; - break; - } - OWSAssert(result); - return result; -} - -- (void)applyTitleForCall:(TSCall *)call label:(UILabel *)label -{ - OWSAssert(call); - OWSAssert(label); - - [self configureFonts]; - - label.text = [self titleForCall:call]; -} - -- (NSString *)titleForCall:(TSCall *)call -{ - // We don't actually use the `transaction` but other sibling classes do. - switch (call.callType) { - case RPRecentCallTypeIncoming: - case RPRecentCallTypeOutgoing: - case RPRecentCallTypeOutgoingIncomplete: - case RPRecentCallTypeIncomingIncomplete: - return NSLocalizedString(@"CALL_DEFAULT_STATUS", - @"Message recorded in conversation history when local user is making or has completed a call."); - case RPRecentCallTypeIncomingMissed: - case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: - return NSLocalizedString( - @"CALL_MISSED", @"Message recorded in conversation history when local user missed a call."); - case RPRecentCallTypeIncomingDeclined: - return NSLocalizedString( - @"CALL_DECLINED", @"Message recorded in conversation history when local user declined a call."); - } -} - -- (CGFloat)hSpacing -{ - return 8.f; -} - -- (CGFloat)vSpacing -{ - return 6.f; -} - -- (CGSize)titleSize -{ - OWSAssert(self.conversationStyle); - OWSAssert(self.viewItem); - - CGFloat maxTitleWidth = (CGFloat)ceil(self.conversationStyle.maxMessageWidth - - (self.circleSize + self.hSpacing + self.conversationStyle.textInsetHorizontal * 2)); - DDLogVerbose(@"%@ maxTitleWidth %f", self.logTag, maxTitleWidth); - return [self.titleLabel sizeThatFits:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)]; -} - -- (CGSize)cellSizeWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssert(self.conversationStyle); - OWSAssert(self.viewItem); - OWSAssert([self.viewItem.interaction isKindOfClass:[TSCall class]]); - - TSCall *call = (TSCall *)self.viewItem.interaction; - - [self applyTitleForCall:call label:self.titleLabel]; - CGSize titleSize = [self titleSize]; - - CGSize hStackSize = titleSize; - hStackSize.width += (self.hSpacing + self.circleSize); - hStackSize.height = MAX(hStackSize.height, self.circleSize); - - CGSize vStackSize = hStackSize; - if (self.hasFooter) { - CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem]; - vStackSize.height += (self.vSpacing + footerSize.height); - vStackSize.width = MAX(vStackSize.width, footerSize.width); - } - - CGSize result = CGSizeCeil(CGSizeMake( - MIN(self.conversationStyle.viewWidth, vStackSize.width + self.conversationStyle.textInsetHorizontal * 2), - vStackSize.height + self.conversationStyle.textInsetTop + self.conversationStyle.textInsetBottom)); - return result; -} - -#pragma mark - UIMenuController - -- (void)showMenuController -{ - OWSAssertIsOnMainThread(); - - DDLogDebug(@"%@ long pressed call cell: %@", self.logTag, self.viewItem.interaction.debugDescription); - - [self becomeFirstResponder]; - - if ([UIMenuController sharedMenuController].isMenuVisible) { - [[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO]; - } - - UIMenuController *menuController = [UIMenuController sharedMenuController]; - menuController.menuItems = @[]; - UIView *fromView = self.titleLabel; - CGRect targetRect = [fromView.superview convertRect:fromView.frame toView:self]; - [menuController setTargetRect:targetRect inView:self]; - [menuController setMenuVisible:YES animated:YES]; -} - -- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender -{ - return action == @selector(delete:); -} - -- (void) delete:(nullable id)sender -{ - DDLogInfo(@"%@ chose delete", self.logTag); - - TSInteraction *interaction = self.viewItem.interaction; - OWSAssert(interaction); - - [interaction remove]; -} - -- (BOOL)canBecomeFirstResponder -{ - return YES; -} - -- (void)prepareForReuse -{ - [NSLayoutConstraint deactivateConstraints:self.layoutConstraints]; - [self.layoutConstraints removeAllObjects]; - - [self.footerView prepareForReuse]; -} - -#pragma mark - Gesture recognizers - -- (void)handleTapGesture:(UITapGestureRecognizer *)sender -{ - OWSAssert(self.delegate); - OWSAssert([self.viewItem.interaction isKindOfClass:[TSCall class]]); - - if (sender.state == UIGestureRecognizerStateRecognized) { - TSCall *call = (TSCall *)self.viewItem.interaction; - [self.delegate didTapCall:call]; - } -} - -- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)longPress -{ - OWSAssert(self.delegate); - - TSInteraction *interaction = self.viewItem.interaction; - OWSAssert(interaction); - - if (longPress.state == UIGestureRecognizerStateBegan) { - [self showMenuController]; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSSystemMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSSystemMessageCell.m index 4be079f3e..ad78d4155 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSSystemMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSSystemMessageCell.m @@ -11,19 +11,47 @@ #import #import #import +#import #import #import NS_ASSUME_NONNULL_BEGIN +typedef void (^SystemMessageActionBlock)(void); + +@interface SystemMessageAction : NSObject + +@property (nonatomic) NSString *title; +@property (nonatomic) SystemMessageActionBlock block; + +@end + +#pragma mark - + +@implementation SystemMessageAction + ++ (SystemMessageAction *)actionWithTitle:(NSString *)title block:(SystemMessageActionBlock)block +{ + SystemMessageAction *action = [SystemMessageAction new]; + action.title = title; + action.block = block; + return action; +} + +@end + +#pragma mark - + @interface OWSSystemMessageCell () @property (nonatomic, nullable) TSInteraction *interaction; -@property (nonatomic) UIImageView *imageView; +@property (nonatomic) UIImageView *iconView; @property (nonatomic) UILabel *titleLabel; -@property (nonatomic) UIStackView *stackView; +@property (nonatomic) UIButton *button; +@property (nonatomic) UIStackView *vStackView; @property (nonatomic) NSArray *layoutConstraints; +@property (nonatomic, nullable) SystemMessageAction *action; @end @@ -43,38 +71,71 @@ NS_ASSUME_NONNULL_BEGIN - (void)commontInit { - OWSAssert(!self.imageView); + OWSAssert(!self.iconView); self.layoutMargins = UIEdgeInsetsZero; self.contentView.layoutMargins = UIEdgeInsetsZero; - self.imageView = [UIImageView new]; - [self.imageView autoSetDimension:ALDimensionWidth toSize:self.iconSize]; - [self.imageView autoSetDimension:ALDimensionHeight toSize:self.iconSize]; - [self.imageView setContentHuggingHigh]; + self.iconView = [UIImageView new]; + [self.iconView autoSetDimension:ALDimensionWidth toSize:self.iconSize]; + [self.iconView autoSetDimension:ALDimensionHeight toSize:self.iconSize]; + [self.iconView setContentHuggingHigh]; self.titleLabel = [UILabel new]; self.titleLabel.numberOfLines = 0; self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; + self.titleLabel.textAlignment = NSTextAlignmentCenter; - self.stackView = [[UIStackView alloc] initWithArrangedSubviews:@[ - self.imageView, + UIStackView *contentStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + self.iconView, self.titleLabel, ]]; - self.stackView.axis = UILayoutConstraintAxisHorizontal; - self.stackView.spacing = self.hSpacing; - self.stackView.alignment = UIStackViewAlignmentCenter; - [self.contentView addSubview:self.stackView]; - - UITapGestureRecognizer *tap = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; - [self addGestureRecognizer:tap]; + contentStackView.axis = UILayoutConstraintAxisVertical; + contentStackView.spacing = self.iconVSpacing; + contentStackView.alignment = UIStackViewAlignmentCenter; + + self.button = [UIButton buttonWithType:UIButtonTypeCustom]; + [self.button setTitleColor:[UIColor ows_darkSkyBlueColor] forState:UIControlStateNormal]; + self.button.titleLabel.textAlignment = NSTextAlignmentCenter; + [self.button setBackgroundColor:[UIColor ows_light02Color]]; + self.button.layer.cornerRadius = 4.f; + [self.button addTarget:self action:@selector(buttonWasPressed:) forControlEvents:UIControlEventTouchUpInside]; + [self.button autoSetDimension:ALDimensionHeight toSize:self.buttonHeight]; + + self.vStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + contentStackView, + self.button, + ]]; + self.vStackView.axis = UILayoutConstraintAxisVertical; + self.vStackView.spacing = self.buttonVSpacing; + self.vStackView.alignment = UIStackViewAlignmentCenter; + [self.contentView addSubview:self.vStackView]; UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)]; [self addGestureRecognizer:longPress]; } +- (CGFloat)buttonVSpacing +{ + return 7.f; +} + +- (CGFloat)iconVSpacing +{ + return 9.f; +} + +- (CGFloat)buttonHeight +{ + return 40.f; +} + +- (CGFloat)buttonHPadding +{ + return 20.f; +} + - (void)configureFonts { // Update cell to reflect changes in dynamic text. @@ -94,27 +155,43 @@ NS_ASSUME_NONNULL_BEGIN TSInteraction *interaction = self.viewItem.interaction; - UIImage *icon = [self iconForInteraction:interaction]; - self.imageView.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - self.imageView.tintColor = [self iconColorForInteraction:interaction]; + self.action = [self actionForInteraction:interaction]; + + UIImage *_Nullable icon = [self iconForInteraction:interaction]; + if (icon) { + self.iconView.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.iconView.hidden = NO; + self.iconView.tintColor = [self iconColorForInteraction:interaction]; + } else { + self.iconView.hidden = YES; + } + self.titleLabel.textColor = [self textColor]; [self applyTitleForInteraction:interaction label:self.titleLabel transaction:transaction]; - CGSize titleSize = [self titleSize]; + if (self.action) { + [self.button setTitle:self.action.title forState:UIControlStateNormal]; + UIFont *buttonFont = UIFont.ows_dynamicTypeSubheadlineFont.ows_mediumWeight; + self.button.titleLabel.font = buttonFont; + self.button.hidden = NO; + } else { + self.button.hidden = YES; + } + CGSize buttonSize = [self.button sizeThatFits:CGSizeZero]; + [NSLayoutConstraint deactivateConstraints:self.layoutConstraints]; self.layoutConstraints = @[ [self.titleLabel autoSetDimension:ALDimensionWidth toSize:titleSize.width], - [self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.topVMargin], - [self.stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.bottomVMargin], - // H-center the stack. - [self.stackView autoHCenterInSuperview], - [self.stackView autoPinEdgeToSuperviewEdge:ALEdgeLeading - withInset:self.conversationStyle.fullWidthGutterLeading - relation:NSLayoutRelationGreaterThanOrEqual], - [self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing - withInset:self.conversationStyle.fullWidthGutterTrailing - relation:NSLayoutRelationGreaterThanOrEqual], + + [self.button autoSetDimension:ALDimensionWidth toSize:buttonSize.width + self.buttonHPadding * 2.f], + + [self.vStackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.topVMargin], + [self.vStackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.bottomVMargin], + [self.vStackView autoPinEdgeToSuperviewEdge:ALEdgeLeading + withInset:self.conversationStyle.fullWidthGutterLeading], + [self.vStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing + withInset:self.conversationStyle.fullWidthGutterTrailing], ]; } @@ -130,11 +207,10 @@ NS_ASSUME_NONNULL_BEGIN return [UIColor ows_light60Color]; } -- (UIImage *)iconForInteraction:(TSInteraction *)interaction +- (nullable UIImage *)iconForInteraction:(TSInteraction *)interaction { UIImage *result = nil; - // TODO: Don't cast. if ([interaction isKindOfClass:[TSErrorMessage class]]) { switch (((TSErrorMessage *)interaction).errorType) { case TSErrorMessageNonBlockingIdentityChange: @@ -149,8 +225,7 @@ NS_ASSUME_NONNULL_BEGIN case TSErrorMessageInvalidVersion: case TSErrorMessageUnknownContactBlockOffer: case TSErrorMessageGroupCreationFailed: - result = [UIImage imageNamed:@"system_message_info"]; - break; + return nil; } } else if ([interaction isKindOfClass:[TSInfoMessage class]]) { switch (((TSInfoMessage *)interaction).messageType) { @@ -160,28 +235,24 @@ NS_ASSUME_NONNULL_BEGIN case TSInfoMessageAddToContactsOffer: case TSInfoMessageAddUserToProfileWhitelistOffer: case TSInfoMessageAddGroupToProfileWhitelistOffer: - result = [UIImage imageNamed:@"system_message_info"]; - break; case TSInfoMessageTypeGroupUpdate: case TSInfoMessageTypeGroupQuit: - result = [UIImage imageNamed:@"system_message_group"]; - break; case TSInfoMessageTypeDisappearingMessagesUpdate: - result = [UIImage imageNamed:@"ic_timer"]; - break; + return nil; case TSInfoMessageVerificationStateChange: - result = [UIImage imageNamed:@"system_message_verified"]; - OWSAssert([interaction isKindOfClass:[OWSVerificationStateChangeMessage class]]); if ([interaction isKindOfClass:[OWSVerificationStateChangeMessage class]]) { OWSVerificationStateChangeMessage *message = (OWSVerificationStateChangeMessage *)interaction; BOOL isVerified = message.verificationState == OWSVerificationStateVerified; if (!isVerified) { - result = [UIImage imageNamed:@"system_message_info"]; + return nil; } } + result = [UIImage imageNamed:@"system_message_verified"]; break; } + } else if ([interaction isKindOfClass:[TSCall class]]) { + return nil; } else { OWSFail(@"Unknown interaction type: %@", [interaction class]); return nil; @@ -231,6 +302,9 @@ NS_ASSUME_NONNULL_BEGIN } else { label.text = [infoMessage previewTextWithTransaction:transaction]; } + } else if ([interaction isKindOfClass:[TSCall class]]) { + TSCall *call = (TSCall *)interaction; + label.text = [call previewTextWithTransaction:transaction]; } else { OWSFail(@"Unknown interaction type: %@", [interaction class]); label.text = nil; @@ -262,9 +336,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(self.conversationStyle); OWSAssert(self.viewItem); - CGFloat hMargins = (self.conversationStyle.fullWidthGutterLeading + self.conversationStyle.fullWidthGutterTrailing); - CGFloat maxTitleWidth - = (CGFloat)floor(self.conversationStyle.fullWidthContentWidth - (hMargins + self.iconSize + self.hSpacing)); + CGFloat maxTitleWidth = (CGFloat)floor(self.conversationStyle.fullWidthContentWidth); return [self.titleLabel sizeThatFits:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)]; } @@ -277,11 +349,21 @@ NS_ASSUME_NONNULL_BEGIN CGSize result = CGSizeMake(self.conversationStyle.viewWidth, 0); - [self applyTitleForInteraction:interaction label:self.titleLabel transaction:transaction]; + UIImage *_Nullable icon = [self iconForInteraction:interaction]; + if (icon) { + result.height += self.iconSize + self.iconVSpacing; + } + [self applyTitleForInteraction:interaction label:self.titleLabel transaction:transaction]; CGSize titleSize = [self titleSize]; - CGFloat contentHeight = ceil(MAX([self iconSize], titleSize.height)); - result.height = (contentHeight + self.topVMargin + self.bottomVMargin); + result.height += titleSize.height; + + SystemMessageAction *_Nullable action = [self actionForInteraction:interaction]; + if (action) { + result.height += self.buttonHeight + self.buttonVSpacing; + } + + result.height += self.topVMargin + self.bottomVMargin; return result; } @@ -328,19 +410,151 @@ NS_ASSUME_NONNULL_BEGIN return YES; } -#pragma mark - Gesture recognizers +#pragma mark - Actions -- (void)handleTapGesture:(UITapGestureRecognizer *)sender +- (nullable SystemMessageAction *)actionForInteraction:(TSInteraction *)interaction { - OWSAssert(self.delegate); + OWSAssertIsOnMainThread(); + OWSAssert(interaction); - if (sender.state == UIGestureRecognizerStateRecognized) { - TSInteraction *interaction = self.viewItem.interaction; - OWSAssert(interaction); - [self.delegate didTapSystemMessageWithInteraction:interaction]; + if ([interaction isKindOfClass:[TSErrorMessage class]]) { + return [self actionForErrorMessage:(TSErrorMessage *)interaction]; + } else if ([interaction isKindOfClass:[TSInfoMessage class]]) { + return [self actionForInfoMessage:(TSInfoMessage *)interaction]; + } else if ([interaction isKindOfClass:[TSCall class]]) { + return [self actionForCall:(TSCall *)interaction]; + } else { + OWSFail(@"Tap for system messages of unknown type: %@", [interaction class]); + return nil; } } +- (nullable SystemMessageAction *)actionForErrorMessage:(TSErrorMessage *)message +{ + OWSAssert(message); + + __weak OWSSystemMessageCell *weakSelf = self; + switch (message.errorType) { + case TSErrorMessageInvalidKeyException: + return nil; + case TSErrorMessageNonBlockingIdentityChange: + return [SystemMessageAction + actionWithTitle:NSLocalizedString(@"SYSTEM_MESSAGE_ACTION_VERIFY_SAFETY_NUMBER", + @"Label for button to verify a user's safety number.") + block:^{ + [weakSelf.delegate tappedNonBlockingIdentityChangeForRecipientId:message.recipientId]; + }]; + case TSErrorMessageWrongTrustedIdentityKey: + return [SystemMessageAction + actionWithTitle:NSLocalizedString(@"SYSTEM_MESSAGE_ACTION_VERIFY_SAFETY_NUMBER", + @"Label for button to verify a user's safety number.") + block:^{ + [weakSelf.delegate + tappedInvalidIdentityKeyErrorMessage:(TSInvalidIdentityKeyErrorMessage *)message]; + }]; + case TSErrorMessageMissingKeyId: + case TSErrorMessageNoSession: + return nil; + case TSErrorMessageInvalidMessage: + return [SystemMessageAction actionWithTitle:NSLocalizedString(@"FINGERPRINT_SHRED_KEYMATERIAL_BUTTON", @"") + block:^{ + [weakSelf.delegate tappedCorruptedMessage:message]; + }]; + case TSErrorMessageDuplicateMessage: + case TSErrorMessageInvalidVersion: + return nil; + case TSErrorMessageUnknownContactBlockOffer: + OWSFail(@"TSErrorMessageUnknownContactBlockOffer"); + return nil; + case TSErrorMessageGroupCreationFailed: + return [SystemMessageAction actionWithTitle:CommonStrings.retryButton + block:^{ + [weakSelf.delegate resendGroupUpdateForErrorMessage:message]; + }]; + } + + DDLogWarn(@"%@ Unhandled tap for error message:%@", self.logTag, message); + return nil; +} + +- (nullable SystemMessageAction *)actionForInfoMessage:(TSInfoMessage *)message +{ + OWSAssert(message); + + __weak OWSSystemMessageCell *weakSelf = self; + switch (message.messageType) { + case TSInfoMessageUserNotRegistered: + case TSInfoMessageTypeSessionDidEnd: + return nil; + case TSInfoMessageTypeUnsupportedMessage: + // Unused. + return nil; + case TSInfoMessageAddToContactsOffer: + // Unused. + OWSFail(@"TSInfoMessageAddToContactsOffer"); + return nil; + case TSInfoMessageAddUserToProfileWhitelistOffer: + // Unused. + OWSFail(@"TSInfoMessageAddUserToProfileWhitelistOffer"); + return nil; + case TSInfoMessageAddGroupToProfileWhitelistOffer: + // Unused. + OWSFail(@"TSInfoMessageAddGroupToProfileWhitelistOffer"); + return nil; + case TSInfoMessageTypeGroupUpdate: + return nil; + case TSInfoMessageTypeGroupQuit: + return nil; + case TSInfoMessageTypeDisappearingMessagesUpdate: + return [SystemMessageAction actionWithTitle:NSLocalizedString(@"CONVERSATION_SETTINGS_TAP_TO_CHANGE", + @"Label for button that opens conversation settings.") + block:^{ + [weakSelf.delegate showConversationSettings]; + }]; + case TSInfoMessageVerificationStateChange: + return [SystemMessageAction + actionWithTitle:NSLocalizedString(@"SHOW_SAFETY_NUMBER_ACTION", @"Action sheet item") + block:^{ + [weakSelf.delegate + showFingerprintWithRecipientId:((OWSVerificationStateChangeMessage *)message) + .recipientId]; + }]; + } + + DDLogInfo(@"%@ Unhandled tap for info message: %@", self.logTag, message); + return nil; +} + +- (nullable SystemMessageAction *)actionForCall:(TSCall *)call +{ + OWSAssert(call); + + __weak OWSSystemMessageCell *weakSelf = self; + switch (call.callType) { + case RPRecentCallTypeIncoming: + case RPRecentCallTypeIncomingMissed: + case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: + case RPRecentCallTypeIncomingDeclined: + return + [SystemMessageAction actionWithTitle:NSLocalizedString(@"CALLBACK_BUTTON_TITLE", @"notification action") + block:^{ + [weakSelf.delegate handleCallTap:call]; + }]; + case RPRecentCallTypeOutgoing: + case RPRecentCallTypeOutgoingMissed: + return [SystemMessageAction actionWithTitle:NSLocalizedString(@"CALL_AGAIN_BUTTON_TITLE", + @"Label for button that lets users call a contact again.") + block:^{ + [weakSelf.delegate handleCallTap:call]; + }]; + case RPRecentCallTypeOutgoingIncomplete: + case RPRecentCallTypeIncomingIncomplete: + return nil; + } +} + +#pragma mark - Events + - (void)handleLongPressGesture:(UILongPressGestureRecognizer *)longPress { OWSAssert(self.delegate); @@ -353,6 +567,24 @@ NS_ASSUME_NONNULL_BEGIN } } +- (void)buttonWasPressed:(id)sender +{ + if (!self.action.block) { + OWSFail(@"%@ Missing action", self.logTag); + } else { + self.action.block(); + } +} + +#pragma mark - Reuse + +- (void)prepareForReuse +{ + [super prepareForReuse]; + + self.action = nil; +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 25dcc800d..a61cfb319 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -20,7 +20,6 @@ #import "NSAttributedString+OWS.h" #import "NewGroupViewController.h" #import "OWSAudioPlayer.h" -#import "OWSCallMessageCell.h" #import "OWSContactOffersCell.h" #import "OWSConversationSettingsViewController.h" #import "OWSConversationSettingsViewDelegate.h" @@ -642,8 +641,6 @@ typedef enum : NSUInteger { { [self.collectionView registerClass:[OWSSystemMessageCell class] forCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier]]; - [self.collectionView registerClass:[OWSCallMessageCell class] - forCellWithReuseIdentifier:[OWSCallMessageCell cellReuseIdentifier]]; [self.collectionView registerClass:[OWSUnreadIndicatorCell class] forCellWithReuseIdentifier:[OWSUnreadIndicatorCell cellReuseIdentifier]]; [self.collectionView registerClass:[OWSContactOffersCell class] @@ -1866,45 +1863,6 @@ typedef enum : NSUInteger { [self presentViewController:actionSheetController animated:YES completion:nil]; } -- (void)handleErrorMessageTap:(TSErrorMessage *)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: - // Unused. - OWSFail(@"TSErrorMessageUnknownContactBlockOffer"); - return; - case TSErrorMessageGroupCreationFailed: - [self resendGroupUpdateForErrorMessage:message]; - return; - } - - DDLogWarn(@"%@ Unhandled tap for error message:%@", self.logTag, message); -} - - (void)tappedNonBlockingIdentityChangeForRecipientId:(nullable NSString *)signalId { if (signalId == nil) { @@ -1923,46 +1881,6 @@ typedef enum : NSUInteger { [self showFingerprintWithRecipientId:signalId]; } -- (void)handleInfoMessageTap:(TSInfoMessage *)message -{ - OWSAssert(message); - - switch (message.messageType) { - case TSInfoMessageUserNotRegistered: - break; - case TSInfoMessageTypeSessionDidEnd: - break; - case TSInfoMessageTypeUnsupportedMessage: - // Unused. - break; - case TSInfoMessageAddToContactsOffer: - // Unused. - OWSFail(@"TSInfoMessageAddToContactsOffer"); - return; - case TSInfoMessageAddUserToProfileWhitelistOffer: - // Unused. - OWSFail(@"TSInfoMessageAddUserToProfileWhitelistOffer"); - return; - case TSInfoMessageAddGroupToProfileWhitelistOffer: - // Unused. - OWSFail(@"TSInfoMessageAddGroupToProfileWhitelistOffer"); - return; - case TSInfoMessageTypeGroupUpdate: - [self showConversationSettings]; - return; - case TSInfoMessageTypeGroupQuit: - break; - case TSInfoMessageTypeDisappearingMessagesUpdate: - [self showConversationSettings]; - return; - case TSInfoMessageVerificationStateChange: - [self showFingerprintWithRecipientId:((OWSVerificationStateChangeMessage *)message).recipientId]; - break; - } - - DDLogInfo(@"%@ Unhandled tap for info message:%@", self.logTag, message); -} - - (void)tappedCorruptedMessage:(TSErrorMessage *)message { NSString *alertMessage = [NSString @@ -2521,32 +2439,6 @@ typedef enum : NSUInteger { [self.inputToolbar beginEditingTextMessage]; } -#pragma mark - Calls - -- (void)didTapCall:(TSCall *)call -{ - OWSAssertIsOnMainThread(); - OWSAssert([call isKindOfClass:[TSCall class]]); - - [self handleCallTap:call]; -} - -#pragma mark - System Messages - -- (void)didTapSystemMessageWithInteraction:(TSInteraction *)interaction -{ - OWSAssertIsOnMainThread(); - OWSAssert(interaction); - - if ([interaction isKindOfClass:[TSErrorMessage class]]) { - [self handleErrorMessageTap:(TSErrorMessage *)interaction]; - } else if ([interaction isKindOfClass:[TSInfoMessage class]]) { - [self handleInfoMessageTap:(TSInfoMessage *)interaction]; - } else { - OWSFail(@"Tap for system messages of unknown type: %@", [interaction class]); - } -} - #pragma mark - ContactEditingDelegate - (void)didFinishEditingContact diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index df22f1ff7..42bb737a5 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -4,7 +4,6 @@ #import "ConversationViewItem.h" #import "OWSAudioMessageView.h" -#import "OWSCallMessageCell.h" #import "OWSContactOffersCell.h" #import "OWSMessageCell.h" #import "OWSSystemMessageCell.h" @@ -231,10 +230,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) break; case OWSInteractionType_Error: case OWSInteractionType_Info: - measurementCell = [OWSSystemMessageCell new]; - break; case OWSInteractionType_Call: - measurementCell = [OWSCallMessageCell new]; + measurementCell = [OWSSystemMessageCell new]; break; case OWSInteractionType_UnreadIndicator: measurementCell = [OWSUnreadIndicatorCell new]; @@ -298,10 +295,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) forIndexPath:indexPath]; case OWSInteractionType_Error: case OWSInteractionType_Info: - return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier] - forIndexPath:indexPath]; case OWSInteractionType_Call: - return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSCallMessageCell cellReuseIdentifier] + return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier] forIndexPath:indexPath]; case OWSInteractionType_UnreadIndicator: return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSUnreadIndicatorCell cellReuseIdentifier] diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index c98ea9c46..9a1ded442 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -3429,6 +3429,10 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac withCallNumber:@"+19174054215" callType:RPRecentCallTypeIncomingDeclined inThread:contactThread]]; + [result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + withCallNumber:@"+19174054215" + callType:RPRecentCallTypeOutgoingMissed + inThread:contactThread]]; } { diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index a14218903..b991847ba 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -1107,6 +1107,14 @@ private class SignalCallData: NSObject { call.state = .localHangup + if let callRecord = call.callRecord { + if callRecord.callType == RPRecentCallTypeOutgoingIncomplete { + callRecord.updateCallType(RPRecentCallTypeOutgoingMissed) + } + } else { + owsFail("\(self.logTag) missing call record in \(#function)") + } + // TODO something like this lifted from Signal-Android. // this.accountManager.cancelInFlightRequests(); // this.messageSender.cancelInFlightRequests(); diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index cc95d4978..d852ebce1 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -284,24 +284,18 @@ /* Button text to enable batch selection mode */ "BUTTON_SELECT" = "Select"; +/* Label for button that lets users call a contact again. */ +"CALL_AGAIN_BUTTON_TITLE" = "Call Again"; + /* Alert message when calling and permissions for microphone are missing */ "CALL_AUDIO_PERMISSION_MESSAGE" = "Signal requires access to your microphone to make calls and record voice messages. You can grant this permission in the Settings app."; /* Alert title when calling and permissions for microphone are missing */ "CALL_AUDIO_PERMISSION_TITLE" = "Microphone Access Required"; -/* Message recorded in conversation history when local user declined a call. */ -"CALL_DECLINED" = "Call Declined"; - -/* Message recorded in conversation history when local user is making or has completed a call. */ -"CALL_DEFAULT_STATUS" = "Contact Called"; - /* Accessibility label for placing call button */ "CALL_LABEL" = "Call"; -/* Message recorded in conversation history when local user missed a call. */ -"CALL_MISSED" = "Missed Call"; - /* Call setup status label after outgoing call times out */ "CALL_SCREEN_STATUS_NO_ANSWER" = "No Answer."; @@ -557,6 +551,9 @@ /* Label for 'new contact' button in conversation settings view. */ "CONVERSATION_SETTINGS_NEW_CONTACT" = "Create New Contact"; +/* Label for button that opens conversation settings. */ +"CONVERSATION_SETTINGS_TAP_TO_CHANGE" = "Tap to Change"; + /* Label for button to unmute a thread. */ "CONVERSATION_SETTINGS_UNMUTE_ACTION" = "Unmute"; @@ -880,7 +877,7 @@ "ERROR_MESSAGE_INVALID_KEY_EXCEPTION" = "The recipient's key is not valid."; /* No comment provided by engineer. */ -"ERROR_MESSAGE_INVALID_MESSAGE" = "Received message was out of sync. Tap to reset your secure session."; +"ERROR_MESSAGE_INVALID_MESSAGE" = "Received message was out of sync."; /* No comment provided by engineer. */ "ERROR_MESSAGE_INVALID_VERSION" = "Received a message not compatible with this version."; @@ -898,7 +895,7 @@ "ERROR_MESSAGE_UNKNOWN_ERROR" = "An unknown error occurred."; /* No comment provided by engineer. */ -"ERROR_MESSAGE_WRONG_TRUSTED_IDENTITY_KEY" = "Safety number changed. Tap to verify."; +"ERROR_MESSAGE_WRONG_TRUSTED_IDENTITY_KEY" = "Safety number changed."; /* Format string for 'unregistered user' error. Embeds {{the unregistered user's name or signal id}}. */ "ERROR_UNREGISTERED_USER_FORMAT" = "Unregistered User: %@"; @@ -1063,7 +1060,7 @@ "INCOMING_DECLINED_CALL" = "You declined a call"; /* No comment provided by engineer. */ -"INCOMING_INCOMPLETE_CALL" = "Incomplete incoming call from"; +"INCOMING_INCOMPLETE_CALL" = "Incoming call"; /* info message text shown in conversation view */ "INFO_MESSAGE_MISSED_CALL_DUE_TO_CHANGED_IDENITY" = "Missed call because their safety number has changed."; @@ -1434,7 +1431,10 @@ "OUTGOING_CALL" = "Outgoing call"; /* No comment provided by engineer. */ -"OUTGOING_INCOMPLETE_CALL" = "Unanswered outgoing call"; +"OUTGOING_INCOMPLETE_CALL" = "Outgoing call"; + +/* info message recorded in conversation history when local user tries and fails to call another user. */ +"OUTGOING_MISSED_CALL" = "Unanswered outgoing call"; /* A display format for oversize text messages. */ "OVERSIZE_TEXT_DISPLAY_FORMAT" = "%@…"; @@ -2112,7 +2112,7 @@ "SHARE_EXTENSION_VIEW_TITLE" = "Share to Signal"; /* Action sheet item */ -"SHOW_SAFETY_NUMBER_ACTION" = "Show New Safety Number"; +"SHOW_SAFETY_NUMBER_ACTION" = "Show Safety Number"; /* notification action */ "SHOW_THREAD_BUTTON_TITLE" = "Show Conversation"; @@ -2129,6 +2129,9 @@ /* No comment provided by engineer. */ "SUCCESSFUL_VERIFICATION_TITLE" = "Safety Number Matches!"; +/* Label for button to verify a user's safety number. */ +"SYSTEM_MESSAGE_ACTION_VERIFY_SAFETY_NUMBER" = "Verify Safety Number"; + /* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_DAYS" = "%@ days"; diff --git a/SignalMessaging/utils/ConversationStyle.swift b/SignalMessaging/utils/ConversationStyle.swift index 97ccc9009..a91841a30 100644 --- a/SignalMessaging/utils/ConversationStyle.swift +++ b/SignalMessaging/utils/ConversationStyle.swift @@ -170,11 +170,6 @@ public class ConversationStyle: NSObject { } } - @objc - public func bubbleColor(call: TSCall) -> UIColor { - return bubbleColor(isIncoming: call.isIncoming) - } - @objc public func bubbleColor(isIncoming: Bool) -> UIColor { if isIncoming { @@ -200,11 +195,6 @@ public class ConversationStyle: NSObject { } } - @objc - public func bubbleTextColor(call: TSCall) -> UIColor { - return bubbleTextColor(isIncoming: call.isIncoming) - } - @objc public func bubbleTextColor(isIncoming: Bool) -> UIColor { if isIncoming { diff --git a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h b/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h new file mode 100644 index 000000000..76c401a44 --- /dev/null +++ b/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h @@ -0,0 +1,29 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +@class OWSPrimaryStorage; +@class OWSStorage; + +@interface OWSIncompleteCallsJob : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; + +- (void)run; + ++ (NSString *)databaseExtensionName; ++ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage; + +#ifdef DEBUG +/** + * Only use the sync version for testing, generally we'll want to register extensions async + */ +- (void)blockingRegisterDatabaseExtensions; +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m b/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m new file mode 100644 index 000000000..5592bf8d1 --- /dev/null +++ b/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m @@ -0,0 +1,150 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSIncompleteCallsJob.h" +#import "OWSPrimaryStorage.h" +#import "TSCall.h" +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +static NSString *const OWSIncompleteCallsJobCallTypeColumn = @"call_type"; +static NSString *const OWSIncompleteCallsJobCallTypeIndex = @"index_calls_on_call_type"; + +@interface OWSIncompleteCallsJob () + +@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; + +@end + +#pragma mark - + +@implementation OWSIncompleteCallsJob + +- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage +{ + self = [super init]; + if (!self) { + return self; + } + + _primaryStorage = primaryStorage; + + return self; +} + +- (NSArray *)fetchIncompleteCallIdsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +{ + OWSAssert(transaction); + + NSMutableArray *messageIds = [NSMutableArray new]; + + NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ == %d OR %@ == %d", + OWSIncompleteCallsJobCallTypeColumn, + (int)RPRecentCallTypeOutgoingIncomplete, + OWSIncompleteCallsJobCallTypeColumn, + (int)RPRecentCallTypeIncomingIncomplete]; + YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; + [[transaction ext:OWSIncompleteCallsJobCallTypeIndex] + enumerateKeysMatchingQuery:query + usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { + [messageIds addObject:key]; + }]; + + return [messageIds copy]; +} + +- (void)enumerateIncompleteCallsWithBlock:(void (^)(TSCall *call))block + transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + OWSAssert(transaction); + + // Since we can't directly mutate the enumerated "incomplete" calls, we store only their ids in hopes + // of saving a little memory and then enumerate the (larger) TSCall objects one at a time. + for (NSString *callId in [self fetchIncompleteCallIdsWithTransaction:transaction]) { + TSCall *_Nullable call = [TSCall fetchObjectWithUniqueID:callId transaction:transaction]; + if ([call isKindOfClass:[TSCall class]]) { + block(call); + } else { + DDLogError(@"%@ unexpected object: %@", self.logTag, call); + } + } +} + +- (void)run +{ + __block uint count = 0; + + [[self.primaryStorage newDatabaseConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self + enumerateIncompleteCallsWithBlock:^(TSCall *call) { + if (call.callType == RPRecentCallTypeOutgoingIncomplete) { + DDLogDebug(@"%@ marking call as missed: %@", self.logTag, call.uniqueId); + [call updateCallType:RPRecentCallTypeOutgoingMissed transaction:transaction]; + OWSAssert(call.callType == RPRecentCallTypeOutgoingMissed); + } else if (call.callType == RPRecentCallTypeIncomingIncomplete) { + DDLogDebug(@"%@ marking call as missed: %@", self.logTag, call.uniqueId); + [call updateCallType:RPRecentCallTypeIncomingMissed transaction:transaction]; + OWSAssert(call.callType == RPRecentCallTypeIncomingMissed); + } else { + OWSProdLogAndFail( + @"%@ call has unexpected call type: %@", self.logTag, NSStringFromCallType(call.callType)); + return; + } + count++; + } + transaction:transaction]; + }]; + + DDLogDebug(@"%@ Marked %u calls as missed", self.logTag, count); +} + +#pragma mark - YapDatabaseExtension + ++ (YapDatabaseSecondaryIndex *)indexDatabaseExtension +{ + YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; + [setup addColumn:OWSIncompleteCallsJobCallTypeColumn withType:YapDatabaseSecondaryIndexTypeInteger]; + + YapDatabaseSecondaryIndexHandler *handler = + [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction, + NSMutableDictionary *dict, + NSString *collection, + NSString *key, + id object) { + if (![object isKindOfClass:[TSCall class]]) { + return; + } + TSCall *call = (TSCall *)object; + + dict[OWSIncompleteCallsJobCallTypeColumn] = @(call.callType); + }]; + + return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler versionTag:nil]; +} + +#ifdef DEBUG +// Useful for tests, don't use in app startup path because it's slow. +- (void)blockingRegisterDatabaseExtensions +{ + [self.primaryStorage registerExtension:[self.class indexDatabaseExtension] + withName:OWSIncompleteCallsJobCallTypeIndex]; +} +#endif + ++ (NSString *)databaseExtensionName +{ + return OWSIncompleteCallsJobCallTypeIndex; +} + ++ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage +{ + [storage asyncRegisterExtension:[self indexDatabaseExtension] withName:OWSIncompleteCallsJobCallTypeIndex]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/TSCall.h b/SignalServiceKit/src/Messages/TSCall.h index 76915e7e9..b7c26b366 100644 --- a/SignalServiceKit/src/Messages/TSCall.h +++ b/SignalServiceKit/src/Messages/TSCall.h @@ -17,15 +17,16 @@ typedef enum { RPRecentCallTypeOutgoingIncomplete, RPRecentCallTypeIncomingIncomplete, RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity, - RPRecentCallTypeIncomingDeclined + RPRecentCallTypeIncomingDeclined, + RPRecentCallTypeOutgoingMissed, } RPRecentCallType; +NSString *NSStringFromCallType(RPRecentCallType callType); + @interface TSCall : TSInteraction @property (nonatomic, readonly) RPRecentCallType callType; -@property (nonatomic, readonly) BOOL isIncoming; - - (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE; - (instancetype)initWithTimestamp:(uint64_t)timestamp @@ -36,6 +37,7 @@ typedef enum { - (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - (void)updateCallType:(RPRecentCallType)callType; +- (void)updateCallType:(RPRecentCallType)callType transaction:(YapDatabaseReadWriteTransaction *)transaction; @end diff --git a/SignalServiceKit/src/Messages/TSCall.m b/SignalServiceKit/src/Messages/TSCall.m index db45d0c56..706afc528 100644 --- a/SignalServiceKit/src/Messages/TSCall.m +++ b/SignalServiceKit/src/Messages/TSCall.m @@ -9,6 +9,28 @@ NS_ASSUME_NONNULL_BEGIN +NSString *NSStringFromCallType(RPRecentCallType callType) +{ + switch (callType) { + case RPRecentCallTypeIncoming: + return @"RPRecentCallTypeIncoming"; + case RPRecentCallTypeOutgoing: + return @"RPRecentCallTypeOutgoing"; + case RPRecentCallTypeIncomingMissed: + return @"RPRecentCallTypeIncomingMissed"; + case RPRecentCallTypeOutgoingIncomplete: + return @"RPRecentCallTypeOutgoingIncomplete"; + case RPRecentCallTypeIncomingIncomplete: + return @"RPRecentCallTypeIncomingIncomplete"; + case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: + return @"RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity"; + case RPRecentCallTypeIncomingDeclined: + return @"RPRecentCallTypeIncomingDeclined"; + case RPRecentCallTypeOutgoingMissed: + return @"RPRecentCallTypeOutgoingMissed"; + } +} + NSUInteger TSCallCurrentSchemaVersion = 1; @interface TSCall () @@ -36,8 +58,11 @@ NSUInteger TSCallCurrentSchemaVersion = 1; _callSchemaVersion = TSCallCurrentSchemaVersion; _callType = callType; - if (_callType == RPRecentCallTypeIncomingMissed - || _callType == RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity) { + + // Ensure users are notified of missed calls. + BOOL isIncomingMissed = (_callType == RPRecentCallTypeIncomingMissed + || _callType == RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity); + if (isIncomingMissed) { _read = NO; } else { _read = YES; @@ -87,6 +112,9 @@ NSUInteger TSCallCurrentSchemaVersion = 1; case RPRecentCallTypeIncomingDeclined: return NSLocalizedString(@"INCOMING_DECLINED_CALL", @"info message recorded in conversation history when local user declined a call"); + case RPRecentCallTypeOutgoingMissed: + return NSLocalizedString(@"OUTGOING_MISSED_CALL", + @"info message recorded in conversation history when local user tries and fails to call another user."); } } @@ -126,35 +154,28 @@ NSUInteger TSCallCurrentSchemaVersion = 1; - (void)updateCallType:(RPRecentCallType)callType { - DDLogInfo(@"%@ updating call type of call: %d with uniqueId: %@ which has timestamp: %llu", + [self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self updateCallType:callType transaction:transaction]; + }]; +} + +- (void)updateCallType:(RPRecentCallType)callType transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + OWSAssert(transaction); + + DDLogInfo(@"%@ updating call type of call: %@ -> %@ with uniqueId: %@ which has timestamp: %llu", self.logTag, - (int)self.callType, + NSStringFromCallType(_callType), + NSStringFromCallType(callType), self.uniqueId, self.timestamp); _callType = callType; - [self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self saveWithTransaction:transaction]; - - // redraw any thread-related unread count UI. - [self touchThreadWithTransaction:transaction]; - }]; -} + [self saveWithTransaction:transaction]; -- (BOOL)isIncoming -{ - switch (self.callType) { - case RPRecentCallTypeIncoming: - case RPRecentCallTypeIncomingMissed: - case RPRecentCallTypeIncomingIncomplete: - case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: - case RPRecentCallTypeIncomingDeclined: - return YES; - case RPRecentCallTypeOutgoing: - case RPRecentCallTypeOutgoingIncomplete: - return NO; - } + // redraw any thread-related unread count UI. + [self touchThreadWithTransaction:transaction]; } @end diff --git a/SignalServiceKit/src/Storage/OWSPrimaryStorage.m b/SignalServiceKit/src/Storage/OWSPrimaryStorage.m index d3f43d360..9cfacba44 100644 --- a/SignalServiceKit/src/Storage/OWSPrimaryStorage.m +++ b/SignalServiceKit/src/Storage/OWSPrimaryStorage.m @@ -11,6 +11,7 @@ #import "OWSFailedMessagesJob.h" #import "OWSFileSystem.h" #import "OWSIncomingMessageFinder.h" +#import "OWSIncompleteCallsJob.h" #import "OWSMediaGalleryFinder.h" #import "OWSMessageReceiver.h" #import "OWSStorage+Subclass.h" @@ -64,9 +65,10 @@ void RunAsyncRegistrationsForStorage(OWSStorage *storage, dispatch_block_t compl [TSDatabaseView asyncRegisterSecondaryDevicesDatabaseView:storage]; [OWSDisappearingMessagesFinder asyncRegisterDatabaseExtensions:storage]; [OWSFailedMessagesJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:storage]; + [OWSIncompleteCallsJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:storage]; [OWSFailedAttachmentDownloadsJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:storage]; [OWSMediaGalleryFinder asyncRegisterDatabaseExtensionsWithPrimaryStorage:storage]; - + // NOTE: Always pass the completion to the _LAST_ of the async database // view registrations. [TSDatabaseView asyncRegisterLazyRestoreAttachmentsDatabaseView:storage completion:completion]; diff --git a/SignalServiceKit/src/Storage/OWSStorage.m b/SignalServiceKit/src/Storage/OWSStorage.m index 4ed028586..459e10606 100644 --- a/SignalServiceKit/src/Storage/OWSStorage.m +++ b/SignalServiceKit/src/Storage/OWSStorage.m @@ -234,7 +234,11 @@ NSString *const kNSUserDefaults_DatabaseExtensionVersionMap = @"kNSUserDefaults_ cannotDecodeObjectOfClassName:(NSString *)name originalClasses:(NSArray *)classNames { - OWSProdLogAndFail(@"%@ Could not decode object: %@", self.logTag, name); + if ([name isEqualToString:@"TSRecipient"]) { + DDLogError(@"%@ Could not decode object: %@", self.logTag, name); + } else { + OWSProdLogAndFail(@"%@ Could not decode object: %@", self.logTag, name); + } OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotDecodeClass]); return [OWSUnknownDBObject class]; }