diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index bcf90fb21..5acdf8c6a 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -4,11 +4,13 @@ #import "AppSettingsViewController.h" #import "AttachmentSharing.h" +#import "DateUtil.h" #import "DebugUIPage.h" #import "Environment.h" #import "FLAnimatedImage.h" #import "FingerprintViewController.h" #import "HomeViewController.h" +#import "NSAttributedString+OWS.h" #import "NotificationsManager.h" #import "OWSAnyTouchGestureRecognizer.h" #import "OWSAudioAttachmentPlayer.h" diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index c64044909..3c16f23ef 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -8,6 +8,7 @@ #import "BlockListUIUtils.h" #import "BlockListViewController.h" #import "ContactsViewHelper.h" +#import "DateUtil.h" #import "DebugUITableViewController.h" #import "Environment.h" #import "FingerprintViewController.h" @@ -2151,22 +2152,22 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { return [[NSAttributedString alloc] initWithString:NSLocalizedString(@"MESSAGE_STATUS_FAILED", @"message footer for failed messages")]; } else if (outgoingMessage.messageState == TSOutgoingMessageStateSentToService) { - NSString *text = (outgoingMessage.wasDelivered - ? NSLocalizedString(@"MESSAGE_STATUS_DELIVERED", @"message footer for delivered messages") - : NSLocalizedString(@"MESSAGE_STATUS_SENT", @"message footer for sent messages")); - NSAttributedString *result = [[NSAttributedString alloc] initWithString:text]; - if ([OWSReadReceiptManager.sharedManager areReadReceiptsEnabled] && outgoingMessage.wasDelivered - && outgoingMessage.recipientReadMap.count > 0) { - NSAttributedString *checkmark = [[NSAttributedString alloc] - initWithString:@"\uf00c " - attributes:@{ - NSFontAttributeName : [UIFont ows_fontAwesomeFont:10.f], - NSForegroundColorAttributeName : [UIColor ows_materialBlueColor], - }]; - NSAttributedString *spacing = [[NSAttributedString alloc] initWithString:@" "]; - result = [[checkmark rtlSafeAppend:spacing referenceView:self.view] rtlSafeAppend:result - referenceView:self.view]; + NSString *text; + if (outgoingMessage.wasDelivered) { + NSNumber *_Nullable firstRecipientReadTimestamp = [outgoingMessage firstRecipientReadTimestamp]; + if ([OWSReadReceiptManager.sharedManager areReadReceiptsEnabled] && firstRecipientReadTimestamp) { + text = NSLocalizedString(@"MESSAGE_STATUS_READ", @"message footer for read messages"); + text = [text rtlSafeAppend:@" " referenceView:self.view]; + text = [text rtlSafeAppend:[DateUtil formatPastTimestampRelativeToNow:firstRecipientReadTimestamp + .unsignedLongLongValue] + referenceView:self.view]; + } else { + text = NSLocalizedString(@"MESSAGE_STATUS_DELIVERED", @"message footer for delivered messages"); + } + } else { + text = NSLocalizedString(@"MESSAGE_STATUS_SENT", @"message footer for sent messages"); } + NSAttributedString *result = [[NSAttributedString alloc] initWithString:text]; // Show when it's the last message in the thread if (indexPath.item == [self.collectionView numberOfItemsInSection:indexPath.section] - 1) { diff --git a/Signal/src/ViewControllers/MessageMetadataViewController.swift b/Signal/src/ViewControllers/MessageMetadataViewController.swift index bb0abeb70..a53a7011b 100644 --- a/Signal/src/ViewControllers/MessageMetadataViewController.swift +++ b/Signal/src/ViewControllers/MessageMetadataViewController.swift @@ -21,14 +21,6 @@ class MessageMetadataViewController: OWSViewController { var attachmentStream: TSAttachmentStream? var messageBody: String? - static let dateFormatter: DateFormatter = CreateDateFormatter() - private class func CreateDateFormatter() -> DateFormatter { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .short - dateFormatter.timeStyle = .long - return dateFormatter - } - // MARK: Initializers @available(*, unavailable, message:"use message: constructor instead.") @@ -127,16 +119,14 @@ class MessageMetadataViewController: OWSViewController { } } - let sentDate = NSDate.ows_date(withMillisecondsSince1970:message.timestamp) rows.append(valueRow(name: NSLocalizedString("MESSAGE_METADATA_VIEW_SENT_DATE_TIME", comment: "Label for the 'sent date & time' field of the 'message metadata' view."), - value:MessageMetadataViewController.dateFormatter.string(from:sentDate))) + value:DateUtil.formatPastTimestampRelative(toNow:message.timestamp))) if let _ = message as? TSIncomingMessage { - let receivedDate = message.dateForSorting() rows.append(valueRow(name: NSLocalizedString("MESSAGE_METADATA_VIEW_RECEIVED_DATE_TIME", comment: "Label for the 'received date & time' field of the 'message metadata' view."), - value:MessageMetadataViewController.dateFormatter.string(from:receivedDate))) + value:DateUtil.formatPastTimestampRelative(toNow:message.timestampForSorting()))) } // TODO: We could include the "disappearing messages" state here. @@ -287,19 +277,18 @@ class MessageMetadataViewController: OWSViewController { let recipientReadMap = message.recipientReadMap if let readTimestamp = recipientReadMap[recipientId] { assert(message.messageState == .sentToService) - let readDate = NSDate.ows_date(withMillisecondsSince1970:readTimestamp.uint64Value) - return String(format:NSLocalizedString("MESSAGE_STATUS_READ_WITH_TIMESTAMP_FORMAT", - comment: "message status for messages read by the recipient. Embeds: {{the date and time the message was read}}."), - MessageMetadataViewController.dateFormatter.string(from:readDate)) + return NSLocalizedString("MESSAGE_STATUS_READ", comment:"message footer for read messages").rtlSafeAppend(" ", referenceView:self.view) + .rtlSafeAppend( + DateUtil.formatPastTimestampRelative(toNow:readTimestamp.uint64Value), referenceView:self.view) } let recipientDeliveryMap = message.recipientDeliveryMap if let deliveryTimestamp = recipientDeliveryMap[recipientId] { assert(message.messageState == .sentToService) - let deliveryDate = NSDate.ows_date(withMillisecondsSince1970:deliveryTimestamp.uint64Value) - return String(format:NSLocalizedString("MESSAGE_STATUS_DELIVERED_WITH_TIMESTAMP_FORMAT", - comment: "message status for messages delivered to the recipient. Embeds: {{the date and time the message was delivered}}."), - MessageMetadataViewController.dateFormatter.string(from:deliveryDate)) + return NSLocalizedString("MESSAGE_STATUS_DELIVERED", + comment:"message status for message delivered to their recipient.").rtlSafeAppend(" ", referenceView:self.view) + .rtlSafeAppend( + DateUtil.formatPastTimestampRelative(toNow:deliveryTimestamp.uint64Value), referenceView:self.view) } if message.wasDelivered { diff --git a/Signal/src/util/DateUtil.h b/Signal/src/util/DateUtil.h index 268470429..768a5c67f 100644 --- a/Signal/src/util/DateUtil.h +++ b/Signal/src/util/DateUtil.h @@ -5,10 +5,12 @@ @interface DateUtil : NSObject + (NSDateFormatter *)dateFormatter; -+ (NSDateFormatter *)weekdayFormatter; +//+ (NSDateFormatter *)weekdayFormatter; + (NSDateFormatter *)timeFormatter; + (BOOL)dateIsOlderThanOneDay:(NSDate *)date; + (BOOL)dateIsOlderThanOneWeek:(NSDate *)date; + (BOOL)dateIsToday:(NSDate *)date; ++ (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp; + @end diff --git a/Signal/src/util/DateUtil.m b/Signal/src/util/DateUtil.m index 578e62693..588f9b0f9 100644 --- a/Signal/src/util/DateUtil.m +++ b/Signal/src/util/DateUtil.m @@ -53,4 +53,36 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE"; return [self date:[NSDate date] isEqualToDateIgnoringTime:date]; } ++ (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp +{ + OWSCAssert(pastTimestamp > 0); + + uint64_t nowTimestamp = [NSDate ows_millisecondTimeStamp]; + if (pastTimestamp >= nowTimestamp) { + OWSCFail(@"%@ Unexpected timestamp", self.tag); + return NSLocalizedString(@"TIME_NOW", @"Indicates that the event happened now."); + } + + NSDate *pastDate = [NSDate ows_dateWithMillisecondsSince1970:pastTimestamp]; + if ([self dateIsToday:pastDate]) { + return [[self timeFormatter] stringFromDate:pastDate]; + } else if (![self dateIsOlderThanOneWeek:pastDate]) { + return [[self weekdayFormatter] stringFromDate:pastDate]; + } else { + return [[self dateFormatter] stringFromDate:pastDate]; + } +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + @end diff --git a/Signal/src/util/NSAttributedString+OWS.h b/Signal/src/util/NSAttributedString+OWS.h index a1b32e17e..883684d88 100644 --- a/Signal/src/util/NSAttributedString+OWS.h +++ b/Signal/src/util/NSAttributedString+OWS.h @@ -4,6 +4,12 @@ NS_ASSUME_NONNULL_BEGIN +@interface NSString (OWS) + +- (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView; + +@end + @interface NSAttributedString (OWS) - (NSAttributedString *)rtlSafeAppend:(NSAttributedString *)string referenceView:(UIView *)referenceView; diff --git a/Signal/src/util/NSAttributedString+OWS.m b/Signal/src/util/NSAttributedString+OWS.m index 6ffea8956..269b6c829 100644 --- a/Signal/src/util/NSAttributedString+OWS.m +++ b/Signal/src/util/NSAttributedString+OWS.m @@ -7,6 +7,22 @@ NS_ASSUME_NONNULL_BEGIN +@implementation NSString (OWS) + +- (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView +{ + OWSAssert(string); + OWSAssert(referenceView); + + if ([referenceView isRTL]) { + return [string stringByAppendingString:self]; + } else { + return [self stringByAppendingString:string]; + } +} + +@end + @implementation NSAttributedString (OWS) - (NSAttributedString *)rtlSafeAppend:(NSAttributedString *)string referenceView:(UIView *)referenceView diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index d9043c9c1..01a506f1d 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -839,6 +839,9 @@ /* message footer for failed messages */ "MESSAGE_STATUS_FAILED" = "Sending failed. Tap for info."; +/* message footer for read messages */ +"MESSAGE_STATUS_READ" = "Read"; + /* message status for messages read by the recipient. Embeds: {{the date and time the message was read}}. */ "MESSAGE_STATUS_READ_WITH_TIMESTAMP_FORMAT" = "Read %@"; @@ -1520,6 +1523,9 @@ /* {{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings */ "TIME_AMOUNT_WEEKS" = "%@ weeks"; +/* Indicates that the event happened now. */ +"TIME_NOW" = "Now"; + /* Label for the cancel button in an alert or action sheet. */ "TXT_CANCEL_TITLE" = "Cancel"; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h index a1835a5e5..82aeb7a01 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h @@ -187,6 +187,7 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) { - (void)updateWithReadRecipientId:(NSString *)recipientId readTimestamp:(uint64_t)readTimestamp transaction:(YapDatabaseReadWriteTransaction *)transaction; +- (nullable NSNumber *)firstRecipientReadTimestamp; #pragma mark - Sent Recipients diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index bfcdf9d90..2e038eb2b 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -435,6 +435,17 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec }]; } +- (nullable NSNumber *)firstRecipientReadTimestamp +{ + NSNumber *result = nil; + for (NSNumber *timestamp in self.recipientReadMap.allValues) { + if (!result || (result.unsignedLongLongValue > timestamp.unsignedLongLongValue)) { + result = timestamp; + } + } + return result; +} + #pragma mark - - (OWSSignalServiceProtosDataMessageBuilder *)dataMessageBuilder diff --git a/SignalServiceKit/src/Util/NSDate+OWS.h b/SignalServiceKit/src/Util/NSDate+OWS.h index 104c20892..c04a47b2f 100755 --- a/SignalServiceKit/src/Util/NSDate+OWS.h +++ b/SignalServiceKit/src/Util/NSDate+OWS.h @@ -24,6 +24,8 @@ NS_ASSUME_NONNULL_BEGIN + (NSDate *)ows_dateWithMillisecondsSince1970:(uint64_t)milliseconds; + (uint64_t)ows_millisecondsSince1970ForDate:(NSDate *)date; ++ (NSString *)formatPastTimestampRelativeToNow:(uint64_t)timestamp; + @end NS_ASSUME_NONNULL_END