diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6a8815610..32ba0be18 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -166,6 +166,8 @@ B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82149B725D60393009C0F2A /* BlockedModal.swift */; }; B82149C125D605C6009C0F2A /* InfoBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82149C025D605C6009C0F2A /* InfoBanner.swift */; }; B8214A2B25D63EB9009C0F2A /* MessagesTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8214A2A25D63EB9009C0F2A /* MessagesTableView.swift */; }; + B822F9BD26B26CA2003B8CB8 /* TSCall.m in Sources */ = {isa = PBXBuildFile; fileRef = B822F9BC26B26CA2003B8CB8 /* TSCall.m */; }; + B822F9BE26B26CA8003B8CB8 /* TSCall.h in Headers */ = {isa = PBXBuildFile; fileRef = B822F9BB26B26C82003B8CB8 /* TSCall.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D2825C7A4B400488AB4 /* InputView.swift */; }; B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3225C7A8C600488AB4 /* InputViewButton.swift */; }; B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3C25C7B34D00488AB4 /* InputTextView.swift */; }; @@ -1183,6 +1185,8 @@ B82149B725D60393009C0F2A /* BlockedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedModal.swift; sourceTree = ""; }; B82149C025D605C6009C0F2A /* InfoBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoBanner.swift; sourceTree = ""; }; B8214A2A25D63EB9009C0F2A /* MessagesTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesTableView.swift; sourceTree = ""; }; + B822F9BB26B26C82003B8CB8 /* TSCall.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSCall.h; sourceTree = ""; }; + B822F9BC26B26CA2003B8CB8 /* TSCall.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TSCall.m; sourceTree = ""; }; B8269D2825C7A4B400488AB4 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = ""; }; B8269D3225C7A8C600488AB4 /* InputViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputViewButton.swift; sourceTree = ""; }; B8269D3C25C7B34D00488AB4 /* InputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = ""; }; @@ -2670,6 +2674,8 @@ C32C5A99256DBDC1003C73A2 /* Signal */ = { isa = PBXGroup; children = ( + B822F9BB26B26C82003B8CB8 /* TSCall.h */, + B822F9BC26B26CA2003B8CB8 /* TSCall.m */, C33FDB9C255A581300E217F9 /* TSIncomingMessage.h */, C33FDA97255A57FE00E217F9 /* TSIncomingMessage.m */, C3B7845C25649DA600ADB2E7 /* TSIncomingMessage+Conversion.swift */, @@ -3856,6 +3862,7 @@ C3A3A145256E1B49004D228D /* OWSMediaGalleryFinder.h in Headers */, B8856E33256F18D5001CE70E /* OWSStorage+Subclass.h in Headers */, C32C5C0A256DC9B4003C73A2 /* OWSIdentityManager.h in Headers */, + B822F9BE26B26CA8003B8CB8 /* TSCall.h in Headers */, B8856E9D256F1C3D001CE70E /* OWSSounds.h in Headers */, C32C5E64256DDFD6003C73A2 /* OWSStorage.h in Headers */, ); @@ -4851,6 +4858,7 @@ B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */, C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, + B822F9BD26B26CA2003B8CB8 /* TSCall.m in Sources */, C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */, C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */, C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */, diff --git a/SessionMessagingKit/Messages/Signal/TSCall.h b/SessionMessagingKit/Messages/Signal/TSCall.h new file mode 100644 index 000000000..c2adb5d00 --- /dev/null +++ b/SessionMessagingKit/Messages/Signal/TSCall.h @@ -0,0 +1,58 @@ +// +// Copyright (c) 2021 Open Whisper Systems. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class TSContactThread; + +typedef NS_ENUM(NSUInteger, RPRecentCallType) { + RPRecentCallTypeIncoming = 1, + RPRecentCallTypeOutgoing, + RPRecentCallTypeIncomingMissed, + // These call types are used until the call connects. + RPRecentCallTypeOutgoingIncomplete, + RPRecentCallTypeIncomingIncomplete, + RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity, + RPRecentCallTypeIncomingDeclined, + RPRecentCallTypeOutgoingMissed, + RPRecentCallTypeIncomingAnsweredElsewhere, + RPRecentCallTypeIncomingDeclinedElsewhere, + RPRecentCallTypeIncomingBusyElsewhere +}; + +typedef NS_CLOSED_ENUM(NSUInteger, TSRecentCallOfferType) { TSRecentCallOfferTypeAudio, TSRecentCallOfferTypeVideo }; + +NSString *NSStringFromCallType(RPRecentCallType callType); + +@interface TSCall : TSInteraction + +@property (nonatomic, readonly) RPRecentCallType callType; +@property (nonatomic, readonly) TSRecentCallOfferType offerType; + +- (instancetype)initWithUniqueId:(NSString *)uniqueId + timestamp:(uint64_t)timestamp + thread:(TSThread *)thread NS_UNAVAILABLE; +- (instancetype)initWithUniqueId:(NSString *)uniqueId + timestamp:(uint64_t)timestamp + receivedAtTimestamp:(uint64_t)receivedAtTimestamp + thread:(TSThread *)thread NS_UNAVAILABLE; +- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread NS_UNAVAILABLE; + +- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithCallType:(RPRecentCallType)callType + offerType:(TSRecentCallOfferType)offerType + thread:(TSContactThread *)thread + sentAtTimestamp:(uint64_t)sentAtTimestamp NS_DESIGNATED_INITIALIZER; + + +- (void)updateCallType:(RPRecentCallType)callType; +- (void)updateCallType:(RPRecentCallType)callType transaction:(YapDatabaseReadWriteTransaction *)transaction; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Messages/Signal/TSCall.m b/SessionMessagingKit/Messages/Signal/TSCall.m new file mode 100644 index 000000000..02329da70 --- /dev/null +++ b/SessionMessagingKit/Messages/Signal/TSCall.m @@ -0,0 +1,168 @@ +// +// Copyright (c) 2021 Open Whisper Systems. All rights reserved. +// + +#import +#import +#import +#import + +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"; + case RPRecentCallTypeIncomingAnsweredElsewhere: + return @"RPRecentCallTypeIncomingAnsweredElsewhere"; + case RPRecentCallTypeIncomingDeclinedElsewhere: + return @"RPRecentCallTypeIncomingDeclinedElsewhere"; + case RPRecentCallTypeIncomingBusyElsewhere: + return @"RPRecentCallTypeIncomingBusyElsewhere"; + } +} + +NSUInteger TSCallCurrentSchemaVersion = 1; + +@interface TSCall () + +@property (nonatomic, getter=wasRead) BOOL read; + +@property (nonatomic, readonly) NSUInteger callSchemaVersion; + +@property (nonatomic) RPRecentCallType callType; +@property (nonatomic) TSRecentCallOfferType offerType; + +@end + +#pragma mark - + +@implementation TSCall + +- (instancetype)initWithCallType:(RPRecentCallType)callType + offerType:(TSRecentCallOfferType)offerType + thread:(TSContactThread *)thread + sentAtTimestamp:(uint64_t)sentAtTimestamp +{ + self = [super initInteractionWithTimestamp:sentAtTimestamp inThread:thread]; + + if (!self) { + return self; + } + + _callSchemaVersion = TSCallCurrentSchemaVersion; + _callType = callType; + _offerType = offerType; + + // Ensure users are notified of missed calls. + BOOL isIncomingMissed = (_callType == RPRecentCallTypeIncomingMissed + || _callType == RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity); + if (isIncomingMissed) { + _read = NO; + } else { + _read = YES; + } + + return self; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + if (!self) { + return self; + } + + if (self.callSchemaVersion < 1) { + // Assume user has already seen any call that predate read-tracking + _read = YES; + } + + _callSchemaVersion = TSCallCurrentSchemaVersion; + + return self; +} + +- (OWSInteractionType)interactionType +{ + return OWSInteractionType_Call; +} + +- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + TSThread *thread = [self threadWithTransaction:transaction]; + TSContactThread *contactThread = (TSContactThread *)thread; + NSString *sessionID = contactThread.contactSessionID; + NSString *shortName = [[LKStorage.shared getContactWithSessionID:sessionID] displayNameFor:SNContactContextRegular] ?: sessionID; + + // We don't actually use the `transaction` but other sibling classes do. + switch (_callType) { + case RPRecentCallTypeIncoming: + case RPRecentCallTypeIncomingIncomplete: + case RPRecentCallTypeIncomingAnsweredElsewhere: { + NSString *format = NSLocalizedString( + @"INCOMING_CALL_FORMAT", @"info message text in conversation view. {embeds callee name}"); + return [NSString stringWithFormat:format, shortName]; + } + case RPRecentCallTypeOutgoing: + case RPRecentCallTypeOutgoingIncomplete: + case RPRecentCallTypeOutgoingMissed: { + NSString *format = NSLocalizedString( + @"OUTGOING_CALL_FORMAT", @"info message text in conversation view. {embeds callee name}"); + return [NSString stringWithFormat:format, shortName]; + } + case RPRecentCallTypeIncomingMissed: + case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: + case RPRecentCallTypeIncomingBusyElsewhere: + case RPRecentCallTypeIncomingDeclined: + case RPRecentCallTypeIncomingDeclinedElsewhere: + return NSLocalizedString(@"MISSED_CALL", @"info message text in conversation view"); + } +} + +#pragma mark - OWSReadTracking + +- (uint64_t)expireStartedAt { return 0; } + +- (BOOL)shouldAffectUnreadCounts { return YES; } + +- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp sendReadReceipt:(BOOL)sendReadReceipt transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + if (self.read) { return; } + self.read = YES; + [self saveWithTransaction:transaction]; +} + +#pragma mark - Methods + +- (void)updateCallType:(RPRecentCallType)callType +{ + [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self updateCallType:callType transaction:transaction]; + }]; +} + +- (void)updateCallType:(RPRecentCallType)callType transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + self.callType = callType; + [self saveWithTransaction:transaction]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Meta/SessionMessagingKit.h b/SessionMessagingKit/Meta/SessionMessagingKit.h index f989c52e6..0625a858d 100644 --- a/SessionMessagingKit/Meta/SessionMessagingKit.h +++ b/SessionMessagingKit/Meta/SessionMessagingKit.h @@ -39,6 +39,7 @@ FOUNDATION_EXPORT const unsigned char SessionMessagingKitVersionString[]; #import #import #import +#import #import #import #import