Let senders tap on outgoing messages with per-message expiration (until countdown completes). Countdown starts when message is sent (not sending, failed).

This commit is contained in:
Matthew Chen 2019-07-10 15:04:10 -03:00
parent 4ed4b15118
commit ddcf10bf01
5 changed files with 123 additions and 30 deletions

View File

@ -265,7 +265,8 @@ NS_ASSUME_NONNULL_BEGIN
return pendingColor;
case PerMessageExpirationState_OutgoingSending:
return self.conversationStyle.bubbleColorOutgoingSending;
case PerMessageExpirationState_OutgoingSent:
case PerMessageExpirationState_OutgoingSentAvailable:
case PerMessageExpirationState_OutgoingSentExpired:
return self.conversationStyle.bubbleColorOutgoingSent;
case PerMessageExpirationState_IncomingInvalidContent:
return Theme.backgroundColor;
@ -285,7 +286,8 @@ NS_ASSUME_NONNULL_BEGIN
case PerMessageExpirationState_IncomingAvailable:
case PerMessageExpirationState_OutgoingFailed:
case PerMessageExpirationState_OutgoingSending:
case PerMessageExpirationState_OutgoingSent:
case PerMessageExpirationState_OutgoingSentAvailable:
case PerMessageExpirationState_OutgoingSentExpired:
return NO;
case PerMessageExpirationState_IncomingInvalidContent:
return YES;
@ -309,7 +311,8 @@ NS_ASSUME_NONNULL_BEGIN
case PerMessageExpirationState_OutgoingFailed:
return nil;
case PerMessageExpirationState_OutgoingSending:
case PerMessageExpirationState_OutgoingSent:
case PerMessageExpirationState_OutgoingSentAvailable:
case PerMessageExpirationState_OutgoingSentExpired:
return nil;
case PerMessageExpirationState_IncomingInvalidContent:
return UIColor.ows_destructiveRedColor;
@ -334,7 +337,8 @@ NS_ASSUME_NONNULL_BEGIN
return ConversationStyle.bubbleTextColorIncoming;
case PerMessageExpirationState_OutgoingFailed:
case PerMessageExpirationState_OutgoingSending:
case PerMessageExpirationState_OutgoingSent:
case PerMessageExpirationState_OutgoingSentAvailable:
case PerMessageExpirationState_OutgoingSentExpired:
return ConversationStyle.bubbleTextColorOutgoing;
case PerMessageExpirationState_IncomingInvalidContent:
return Theme.secondaryColor;
@ -359,7 +363,8 @@ NS_ASSUME_NONNULL_BEGIN
case PerMessageExpirationState_OutgoingFailed:
return pendingColor;
case PerMessageExpirationState_OutgoingSending:
case PerMessageExpirationState_OutgoingSent:
case PerMessageExpirationState_OutgoingSentAvailable:
case PerMessageExpirationState_OutgoingSentExpired:
return ConversationStyle.bubbleTextColorOutgoing;
case PerMessageExpirationState_IncomingInvalidContent:
return Theme.secondaryColor;
@ -443,10 +448,17 @@ NS_ASSUME_NONNULL_BEGIN
self.label.text = CommonStrings.retryButton;
break;
case PerMessageExpirationState_OutgoingSending:
case PerMessageExpirationState_OutgoingSent:
self.label.text = NSLocalizedString(@"MESSAGE_STATUS_SENDING", @"message status while message is sending.");
break;
case PerMessageExpirationState_OutgoingSentExpired:
self.label.text = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_OUTGOING_MESSAGE",
@"Label for outgoing messages with per-message expiration.");
break;
case PerMessageExpirationState_OutgoingSentAvailable:
self.label.text = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_TAP_TO_VIEW",
@"Label for messages with per-message expiration indicating that "
@"user can tap to view the message's contents.");
break;
case PerMessageExpirationState_IncomingInvalidContent:
self.label.text = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_INVALID_CONTENT",
@"Label for messages with per-message expiration that have invalid content.");
@ -487,8 +499,10 @@ NS_ASSUME_NONNULL_BEGIN
case PerMessageExpirationState_OutgoingFailed:
return @"arrow-down-circle-outline-24";
case PerMessageExpirationState_OutgoingSending:
case PerMessageExpirationState_OutgoingSent:
case PerMessageExpirationState_OutgoingSentExpired:
return @"play-outline-24";
case PerMessageExpirationState_OutgoingSentAvailable:
return @"play-filled-24";
case PerMessageExpirationState_IncomingInvalidContent:
OWSFailDebug(@"Unexpected state.");
return nil;
@ -549,9 +563,10 @@ NS_ASSUME_NONNULL_BEGIN
return self.viewItem.perMessageExpirationState == PerMessageExpirationState_IncomingFailed;
}
- (BOOL)isIncomingAvailable
- (BOOL)isAvailable
{
return self.viewItem.perMessageExpirationState == PerMessageExpirationState_IncomingAvailable;
return (self.viewItem.perMessageExpirationState == PerMessageExpirationState_IncomingAvailable
|| self.viewItem.perMessageExpirationState == PerMessageExpirationState_OutgoingSentAvailable);
}
#pragma mark - Measurement
@ -712,7 +727,7 @@ NS_ASSUME_NONNULL_BEGIN
if (self.isIncomingFailed) {
[self.delegate didTapFailedIncomingAttachment:self.viewItem];
} else if (self.isIncomingAvailable) {
} else if (self.isAvailable) {
[self.delegate didTapAttachmentWithPerMessageExpiration:self.viewItem
attachmentStream:self.viewItem.attachmentStream];
}

View File

@ -32,9 +32,12 @@ typedef NS_ENUM(NSUInteger, PerMessageExpirationState) {
PerMessageExpirationState_IncomingInvalidContent,
PerMessageExpirationState_OutgoingSending,
PerMessageExpirationState_OutgoingFailed,
PerMessageExpirationState_OutgoingSent,
PerMessageExpirationState_OutgoingSentAvailable,
PerMessageExpirationState_OutgoingSentExpired,
};
NSString *NSStringForPerMessageExpirationState(PerMessageExpirationState value);
@class ContactShareViewModel;
@class ConversationViewCell;
@class DisplayableText;

View File

@ -41,6 +41,32 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
}
}
NSString *NSStringForPerMessageExpirationState(PerMessageExpirationState cellType)
{
switch (cellType) {
case PerMessageExpirationState_Unknown:
return @"PerMessageExpirationState_Unknown";
case PerMessageExpirationState_IncomingExpired:
return @"PerMessageExpirationState_IncomingExpired";
case PerMessageExpirationState_IncomingDownloading:
return @"PerMessageExpirationState_IncomingDownloading";
case PerMessageExpirationState_IncomingFailed:
return @"PerMessageExpirationState_IncomingFailed";
case PerMessageExpirationState_IncomingAvailable:
return @"PerMessageExpirationState_IncomingAvailable";
case PerMessageExpirationState_IncomingInvalidContent:
return @"PerMessageExpirationState_IncomingInvalidContent";
case PerMessageExpirationState_OutgoingSending:
return @"PerMessageExpirationState_OutgoingSending";
case PerMessageExpirationState_OutgoingFailed:
return @"PerMessageExpirationState_OutgoingFailed";
case PerMessageExpirationState_OutgoingSentAvailable:
return @"PerMessageExpirationState_OutgoingSentAvailable";
case PerMessageExpirationState_OutgoingSentExpired:
return @"PerMessageExpirationState_OutgoingSentExpired";
}
}
#pragma mark -
@implementation ConversationMediaAlbumItem
@ -856,10 +882,34 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
switch (outgoingMessage.messageState) {
case TSOutgoingMessageStateSending:
self.perMessageExpirationState = PerMessageExpirationState_OutgoingSending;
break;
case TSOutgoingMessageStateFailed:
self.perMessageExpirationState = PerMessageExpirationState_OutgoingFailed;
break;
default:
self.perMessageExpirationState = PerMessageExpirationState_OutgoingSent;
if (message.perMessageExpirationHasExpired) {
self.perMessageExpirationState = PerMessageExpirationState_OutgoingSentExpired;
} else {
NSArray<TSAttachment *> *mediaAttachments = [message mediaAttachmentsWithTransaction:transaction];
// TODO: We currently only support single attachments for messages
// with per-message expiration.
TSAttachment *_Nullable mediaAttachment = mediaAttachments.firstObject;
if ([mediaAttachment isKindOfClass:[TSAttachmentPointer class]]) {
OWSFailDebug(@"Invalid outgoing attachment.");
self.perMessageExpirationState = PerMessageExpirationState_OutgoingSentExpired;
} else if ([mediaAttachment isKindOfClass:[TSAttachmentStream class]]) {
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)mediaAttachment;
if (attachmentStream.isValidVisualMedia
&& (attachmentStream.isImage || attachmentStream.isAnimated)) {
self.perMessageExpirationState = PerMessageExpirationState_OutgoingSentAvailable;
self.attachmentStream = attachmentStream;
} else {
OWSFailDebug(@"Invalid outgoing attachment.");
self.perMessageExpirationState = PerMessageExpirationState_OutgoingSentExpired;
}
}
}
break;
}
self.messageCellType = OWSMessageCellType_PerMessageExpiration;
return;

View File

@ -1429,12 +1429,18 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
};
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[OWSDisappearingMessagesJob sharedJob] startAnyExpirationForMessage:message
TSInteraction *_Nullable latestCopy =
[TSInteraction anyFetchWithUniqueId:message.uniqueId transaction:transaction.asAnyRead];
if (![latestCopy isKindOfClass:[TSOutgoingMessage class]]) {
OWSLogWarn(@"Could not update expiration for deleted message.");
return;
}
TSOutgoingMessage *latestMessage = (TSOutgoingMessage *)latestCopy;
[[OWSDisappearingMessagesJob sharedJob] startAnyExpirationForMessage:latestMessage
expirationStartedAt:[NSDate ows_millisecondTimeStamp]
transaction:transaction];
[PerMessageExpiration expireIfNecessaryWithMessage:message
transaction:transaction.asAnyWrite];
[PerMessageExpiration expireIfNecessaryWithMessage:latestMessage transaction:transaction.asAnyWrite];
}];
if (!message.shouldSyncTranscript) {

View File

@ -37,13 +37,6 @@ public class PerMessageExpiration: NSObject {
@objc
public class func startPerMessageExpiration(forMessage message: TSMessage,
transaction: SDSAnyWriteTransaction) {
AssertIsOnMainThread()
guard message.hasPerMessageExpiration else {
owsFailDebug("Message does not have per-message expiration.")
return
}
// Start expiration using "now" as the read time.
startPerMessageExpiration(forMessage: message,
readTimestamp: nowMs(),
@ -56,6 +49,15 @@ public class PerMessageExpiration: NSObject {
sendSyncMessages: Bool,
transaction: SDSAnyWriteTransaction) {
guard message.hasPerMessageExpiration else {
owsFailDebug("Message does not have per-message expiration.")
return
}
guard !isOutgoingNotYetSent(message: message) else {
Logger.warn("Not starting expiration for not-yet-sent outgoing message.")
return
}
// Make sure that timestamp is not later than now.
let timestamp = min(readTimestamp, nowMs())
@ -131,13 +133,14 @@ public class PerMessageExpiration: NSObject {
for message in messages {
if shouldMessageAutoExpire(message) {
completeExpiration(forMessage: message, transaction: transaction)
} else if isOutgoingSent(message: message) {
completeExpiration(forMessage: message, transaction: transaction)
} else if message.hasPerMessageExpirationStarted {
if shouldResumeNormalExpiration {
// If expiration is started, resume countdown.
schedulePerMessageExpiration(forMessage: message, transaction: transaction)
}
} else if isOutgoingSent(message: message),
!message.hasPerMessageExpirationStarted {
startPerMessageExpiration(forMessage: message, transaction: transaction)
}
}
}
@ -162,15 +165,16 @@ public class PerMessageExpiration: NSObject {
return
}
// If outgoing message and is "sent", expire.
guard !isOutgoingSent(message: message) else {
// If countdown has completed, expire.
guard !hasExpirationCountdownCompleted(message: message) else {
completeExpiration(forMessage: message, transaction: transaction)
return
}
// If countdown has completed, expire.
guard !hasExpirationCountdownCompleted(message: message) else {
completeExpiration(forMessage: message, transaction: transaction)
// If outgoing message and is "sent", start expiration if necessary.
if isOutgoingSent(message: message),
!message.hasPerMessageExpirationStarted {
startPerMessageExpiration(forMessage: message, transaction: transaction)
return
}
}
@ -181,6 +185,10 @@ public class PerMessageExpiration: NSObject {
}
private class func isOutgoingSent(message: TSMessage) -> Bool {
guard message.hasPerMessageExpiration else {
owsFailDebug("Unexpected message.")
return false
}
// If outgoing message and is "sent", expire.
guard let outgoingMessage = message as? TSOutgoingMessage else {
return false
@ -191,6 +199,17 @@ public class PerMessageExpiration: NSObject {
return true
}
private class func isOutgoingNotYetSent(message: TSMessage) -> Bool {
// If outgoing message and is "sent", expire.
guard let outgoingMessage = message as? TSOutgoingMessage else {
return false
}
guard outgoingMessage.messageState != .sent else {
return false
}
return true
}
// We auto-expire messages after 30 days, even if the user hasn't seen them.
private class func shouldMessageAutoExpire(_ message: TSMessage) -> Bool {
let autoExpireDeadlineMs = min(message.timestamp, message.receivedAtTimestamp) + 30 * kDayInMs