Merge branch 'charlesmchen/messageDateTimes'

pull/1/head
Matthew Chen 7 years ago
commit f131c71d90

@ -232,6 +232,10 @@ NS_ASSUME_NONNULL_BEGIN
actionBlock:^{ actionBlock:^{
[DebugUIMessages createSystemMessagesInThread:thread]; [DebugUIMessages createSystemMessagesInThread:thread];
}], }],
[OWSTableItem itemWithTitle:@"Create messages with variety of timestamps"
actionBlock:^{
[DebugUIMessages createTimestampMessagesInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 10 text and system messages" [OWSTableItem itemWithTitle:@"Send 10 text and system messages"
actionBlock:^{ actionBlock:^{
@ -1534,6 +1538,61 @@ NS_ASSUME_NONNULL_BEGIN
}); });
} }
+ (void)createTimestampMessagesInThread:(TSThread *)thread
{
OWSAssert(thread);
uint64_t now = [NSDate ows_millisecondTimeStamp];
NSArray<NSNumber *> *timestamps = @[
@(now + 1 * kHourInMs),
@(now),
@(now - 1 * kHourInMs),
@(now - 12 * kHourInMs),
@(now - 1 * kDayInMs),
@(now - 2 * kDayInMs),
@(now - 3 * kDayInMs),
@(now - 6 * kDayInMs),
@(now - 7 * kDayInMs),
@(now - 8 * kDayInMs),
@(now - 2 * kWeekInMs),
@(now - 1 * kMonthInMs),
@(now - 2 * kMonthInMs),
];
NSMutableArray<NSString *> *recipientIds = [thread.recipientIdentifiers mutableCopy];
[recipientIds removeObject:[TSAccountManager localNumber]];
NSString *recipientId = (recipientIds.count > 0 ? recipientIds.firstObject : @"+19174054215");
[TSStorageManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSNumber *timestamp in timestamps) {
NSString *randomText = [self randomText];
{
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initWithTimestamp:timestamp.unsignedLongLongValue
inThread:thread
authorId:recipientId
sourceDeviceId:0
messageBody:randomText];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
}
{
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:timestamp.unsignedLongLongValue
inThread:thread
messageBody:randomText];
[message saveWithTransaction:transaction];
[message updateWithMessageState:TSOutgoingMessageStateSentToService transaction:transaction];
[message updateWithSentRecipient:recipientId transaction:transaction];
[message updateWithDeliveredToRecipientId:recipientId
deliveryTimestamp:timestamp
transaction:transaction];
[message updateWithReadRecipientId:recipientId
readTimestamp:timestamp.unsignedLongLongValue
transaction:transaction];
}
}
}];
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -211,7 +211,7 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate, Medi
} }
for recipientId in thread.recipientIdentifiers { for recipientId in thread.recipientIdentifiers {
let (recipientStatus, statusMessage) = MessageRecipientStatusUtils.recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage, recipientId: recipientId, referenceView: self.view) let (recipientStatus, shortStatusMessage, longStatusMessage) = MessageRecipientStatusUtils.recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage, recipientId: recipientId, referenceView: self.view)
guard recipientStatus == recipientStatusGroup else { guard recipientStatus == recipientStatusGroup else {
continue continue
@ -229,9 +229,11 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate, Medi
let cell = ContactTableViewCell() let cell = ContactTableViewCell()
cell.configure(withRecipientId: recipientId, contactsManager: self.contactsManager) cell.configure(withRecipientId: recipientId, contactsManager: self.contactsManager)
let statusLabel = UILabel() let statusLabel = UILabel()
statusLabel.text = statusMessage // We use the "short" status message to avoid being redundant with the section title.
statusLabel.text = shortStatusMessage
statusLabel.textColor = UIColor.ows_darkGray statusLabel.textColor = UIColor.ows_darkGray
statusLabel.font = UIFont.ows_footnote() statusLabel.font = UIFont.ows_footnote()
statusLabel.adjustsFontSizeToFitWidth = true
statusLabel.sizeToFit() statusLabel.sizeToFit()
cell.accessoryView = statusLabel cell.accessoryView = statusLabel
cell.autoSetDimension(.height, toSize: ContactTableViewCell.rowHeight()) cell.autoSetDimension(.height, toSize: ContactTableViewCell.rowHeight())
@ -258,12 +260,14 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate, Medi
rows.append(valueRow(name: NSLocalizedString("MESSAGE_METADATA_VIEW_SENT_DATE_TIME", rows.append(valueRow(name: NSLocalizedString("MESSAGE_METADATA_VIEW_SENT_DATE_TIME",
comment: "Label for the 'sent date & time' field of the 'message metadata' view."), comment: "Label for the 'sent date & time' field of the 'message metadata' view."),
value: DateUtil.formatPastTimestampRelativeToNow(message.timestamp))) value: DateUtil.formatPastTimestampRelativeToNow(message.timestamp,
isRTL:self.view.isRTL())))
if message as? TSIncomingMessage != nil { if message as? TSIncomingMessage != nil {
rows.append(valueRow(name: NSLocalizedString("MESSAGE_METADATA_VIEW_RECEIVED_DATE_TIME", rows.append(valueRow(name: NSLocalizedString("MESSAGE_METADATA_VIEW_RECEIVED_DATE_TIME",
comment: "Label for the 'received date & time' field of the 'message metadata' view."), comment: "Label for the 'received date & time' field of the 'message metadata' view."),
value: DateUtil.formatPastTimestampRelativeToNow(message.timestampForSorting()))) value: DateUtil.formatPastTimestampRelativeToNow(message.timestampForSorting(),
isRTL:self.view.isRTL())))
} }
rows += addAttachmentMetadataRows() rows += addAttachmentMetadataRows()

@ -1,5 +1,5 @@
// //
// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
import Foundation import Foundation
@ -56,7 +56,7 @@ class MessageRecipientStatusUtils: NSObject {
public class func recipientStatus(outgoingMessage: TSOutgoingMessage, public class func recipientStatus(outgoingMessage: TSOutgoingMessage,
recipientId: String, recipientId: String,
referenceView: UIView) -> MessageRecipientStatus { referenceView: UIView) -> MessageRecipientStatus {
let (messageRecipientStatus, _) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage, let (messageRecipientStatus, _, _) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage,
recipientId: recipientId, recipientId: recipientId,
referenceView: referenceView) referenceView: referenceView)
return messageRecipientStatus return messageRecipientStatus
@ -64,20 +64,31 @@ class MessageRecipientStatusUtils: NSObject {
// This method is per-recipient and "biased towards success". // This method is per-recipient and "biased towards success".
// See comments above. // See comments above.
public class func statusMessage(outgoingMessage: TSOutgoingMessage, public class func shortStatusMessage(outgoingMessage: TSOutgoingMessage,
recipientId: String, recipientId: String,
referenceView: UIView) -> String { referenceView: UIView) -> String {
let (_, statusMessage) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage, let (_, shortStatusMessage, _) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage,
recipientId: recipientId, recipientId: recipientId,
referenceView: referenceView) referenceView: referenceView)
return statusMessage return shortStatusMessage
}
// This method is per-recipient and "biased towards success".
// See comments above.
public class func longStatusMessage(outgoingMessage: TSOutgoingMessage,
recipientId: String,
referenceView: UIView) -> String {
let (_, _, longStatusMessage) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage,
recipientId: recipientId,
referenceView: referenceView)
return longStatusMessage
} }
// This method is per-recipient and "biased towards success". // This method is per-recipient and "biased towards success".
// See comments above. // See comments above.
public class func recipientStatusAndStatusMessage(outgoingMessage: TSOutgoingMessage, public class func recipientStatusAndStatusMessage(outgoingMessage: TSOutgoingMessage,
recipientId: String, recipientId: String,
referenceView: UIView) -> (MessageRecipientStatus, String) { referenceView: UIView) -> (status: MessageRecipientStatus, shortStatusMessage: String, longStatusMessage: String) {
// Legacy messages don't have "recipient read" state or "per-recipient delivery" state, // Legacy messages don't have "recipient read" state or "per-recipient delivery" state,
// so we fall back to `TSOutgoingMessageState` which is not per-recipient and therefore // so we fall back to `TSOutgoingMessageState` which is not per-recipient and therefore
// might be misleading. // might be misleading.
@ -85,49 +96,53 @@ class MessageRecipientStatusUtils: NSObject {
let recipientReadMap = outgoingMessage.recipientReadMap let recipientReadMap = outgoingMessage.recipientReadMap
if let readTimestamp = recipientReadMap[recipientId] { if let readTimestamp = recipientReadMap[recipientId] {
assert(outgoingMessage.messageState == .sentToService) assert(outgoingMessage.messageState == .sentToService)
let statusMessage = NSLocalizedString("MESSAGE_STATUS_READ", comment:"message footer for read messages").rtlSafeAppend(" ", referenceView:referenceView) let timestampString = DateUtil.formatPastTimestampRelativeToNow(readTimestamp.uint64Value,
.rtlSafeAppend( isRTL:referenceView.isRTL())
DateUtil.formatPastTimestampRelativeToNow(readTimestamp.uint64Value), referenceView:referenceView) let shortStatusMessage = timestampString
return (.read, statusMessage) let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_READ", comment:"message footer for read messages").rtlSafeAppend(" ", referenceView:referenceView)
.rtlSafeAppend(timestampString, referenceView:referenceView)
return (status:.read, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage)
} }
let recipientDeliveryMap = outgoingMessage.recipientDeliveryMap let recipientDeliveryMap = outgoingMessage.recipientDeliveryMap
if let deliveryTimestamp = recipientDeliveryMap[recipientId] { if let deliveryTimestamp = recipientDeliveryMap[recipientId] {
assert(outgoingMessage.messageState == .sentToService) assert(outgoingMessage.messageState == .sentToService)
let statusMessage = NSLocalizedString("MESSAGE_STATUS_DELIVERED", let timestampString = DateUtil.formatPastTimestampRelativeToNow(deliveryTimestamp.uint64Value,
isRTL:referenceView.isRTL())
let shortStatusMessage = timestampString
let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_DELIVERED",
comment:"message status for message delivered to their recipient.").rtlSafeAppend(" ", referenceView:referenceView) comment:"message status for message delivered to their recipient.").rtlSafeAppend(" ", referenceView:referenceView)
.rtlSafeAppend( .rtlSafeAppend(timestampString, referenceView:referenceView)
DateUtil.formatPastTimestampRelativeToNow(deliveryTimestamp.uint64Value), referenceView:referenceView) return (status:.delivered, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage)
return (.delivered, statusMessage)
} }
if outgoingMessage.wasDelivered { if outgoingMessage.wasDelivered {
let statusMessage = NSLocalizedString("MESSAGE_STATUS_DELIVERED", let statusMessage = NSLocalizedString("MESSAGE_STATUS_DELIVERED",
comment:"message status for message delivered to their recipient.") comment:"message status for message delivered to their recipient.")
return (.delivered, statusMessage) return (status:.delivered, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
} }
if outgoingMessage.messageState == .unsent { if outgoingMessage.messageState == .unsent {
let statusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED", comment:"message footer for failed messages") let statusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED", comment:"message footer for failed messages")
return (.failed, statusMessage) return (status:.failed, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
} else if outgoingMessage.messageState == .sentToService || } else if outgoingMessage.messageState == .sentToService ||
outgoingMessage.wasSent(toRecipient:recipientId) { outgoingMessage.wasSent(toRecipient:recipientId) {
let statusMessage = let statusMessage =
NSLocalizedString("MESSAGE_STATUS_SENT", NSLocalizedString("MESSAGE_STATUS_SENT",
comment:"message footer for sent messages") comment:"message footer for sent messages")
return (.sent, statusMessage) return (status:.sent, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
} else if outgoingMessage.hasAttachments() { } else if outgoingMessage.hasAttachments() {
assert(outgoingMessage.messageState == .attemptingOut) assert(outgoingMessage.messageState == .attemptingOut)
let statusMessage = NSLocalizedString("MESSAGE_STATUS_UPLOADING", let statusMessage = NSLocalizedString("MESSAGE_STATUS_UPLOADING",
comment:"message footer while attachment is uploading") comment:"message footer while attachment is uploading")
return (.uploading, statusMessage) return (status:.uploading, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
} else { } else {
assert(outgoingMessage.messageState == .attemptingOut) assert(outgoingMessage.messageState == .attemptingOut)
let statusMessage = NSLocalizedString("MESSAGE_STATUS_SENDING", let statusMessage = NSLocalizedString("MESSAGE_STATUS_SENDING",
comment:"message status while message is sending.") comment:"message status while message is sending.")
return (.sending, statusMessage) return (status:.sending, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
} }
} }

@ -1,7 +1,9 @@
// //
// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
NS_ASSUME_NONNULL_BEGIN
@interface DateUtil : NSObject @interface DateUtil : NSObject
+ (NSDateFormatter *)dateFormatter; + (NSDateFormatter *)dateFormatter;
@ -11,6 +13,8 @@
+ (BOOL)dateIsToday:(NSDate *)date; + (BOOL)dateIsToday:(NSDate *)date;
+ (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp + (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp
NS_SWIFT_NAME(formatPastTimestampRelativeToNow(_:)); isRTL:(BOOL)isRTL NS_SWIFT_NAME(formatPastTimestampRelativeToNow(_:isRTL:));
@end @end
NS_ASSUME_NONNULL_END

@ -1,10 +1,13 @@
// //
// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
#import "DateUtil.h" #import "DateUtil.h"
#import <SignalMessaging/NSString+OWS.h>
#import <SignalServiceKit/NSDate+OWS.h> #import <SignalServiceKit/NSDate+OWS.h>
NS_ASSUME_NONNULL_BEGIN
static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE"; static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
@implementation DateUtil @implementation DateUtil
@ -65,7 +68,13 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return [self date:[NSDate date] isEqualToDateIgnoringTime:date]; return [self date:[NSDate date] isEqualToDateIgnoringTime:date];
} }
+ (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp + (BOOL)dateIsYesterday:(NSDate *)date
{
NSDate *yesterday = [NSDate ows_dateWithMillisecondsSince1970:[NSDate ows_millisecondTimeStamp] - kDayInMs];
return [self date:yesterday isEqualToDateIgnoringTime:date];
}
+ (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp isRTL:(BOOL)isRTL
{ {
OWSCAssert(pastTimestamp > 0); OWSCAssert(pastTimestamp > 0);
@ -73,13 +82,18 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
BOOL isFutureTimestamp = pastTimestamp >= nowTimestamp; BOOL isFutureTimestamp = pastTimestamp >= nowTimestamp;
NSDate *pastDate = [NSDate ows_dateWithMillisecondsSince1970:pastTimestamp]; NSDate *pastDate = [NSDate ows_dateWithMillisecondsSince1970:pastTimestamp];
NSString *dateString;
if (isFutureTimestamp || [self dateIsToday:pastDate]) { if (isFutureTimestamp || [self dateIsToday:pastDate]) {
return [[self timeFormatter] stringFromDate:pastDate]; dateString = NSLocalizedString(@"DATE_TODAY", @"The current day.");
} else if (![self dateIsOlderThanOneWeek:pastDate]) { } else if ([self dateIsYesterday:pastDate]) {
return [[self weekdayFormatter] stringFromDate:pastDate]; dateString = NSLocalizedString(@"DATE_YESTERDAY", @"The day before today.");
} else { } else {
return [[self dateFormatter] stringFromDate:pastDate]; dateString = [[self dateFormatter] stringFromDate:pastDate];
} }
return [[dateString rtlSafeAppend:@" " isRTL:isRTL] rtlSafeAppend:[[self timeFormatter] stringFromDate:pastDate]
isRTL:isRTL];
} }
@end @end
NS_ASSUME_NONNULL_END

@ -493,6 +493,12 @@
/* Title shown while the app is updating its database. */ /* Title shown while the app is updating its database. */
"DATABASE_VIEW_OVERLAY_TITLE" = "Updating Database"; "DATABASE_VIEW_OVERLAY_TITLE" = "Updating Database";
/* The current day. */
"DATE_TODAY" = "Today";
/* The day before today. */
"DATE_YESTERDAY" = "Yesterday";
/* Message indicating that the debug log is being uploaded. */ /* Message indicating that the debug log is being uploaded. */
"DEBUG_LOG_ACTIVITY_INDICATOR" = "Sending Debug Log..."; "DEBUG_LOG_ACTIVITY_INDICATOR" = "Sending Debug Log...";

@ -1,5 +1,5 @@
// //
// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -9,6 +9,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSString *)ows_stripped; - (NSString *)ows_stripped;
- (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView; - (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView;
- (NSString *)rtlSafeAppend:(NSString *)string isRTL:(BOOL)isRTL;
- (NSString *)digitsOnly; - (NSString *)digitsOnly;

@ -1,5 +1,5 @@
// //
// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
#import "NSString+OWS.h" #import "NSString+OWS.h"
@ -19,7 +19,14 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(string); OWSAssert(string);
OWSAssert(referenceView); OWSAssert(referenceView);
if ([referenceView isRTL]) { return [self rtlSafeAppend:string isRTL:referenceView.isRTL];
}
- (NSString *)rtlSafeAppend:(NSString *)string isRTL:(BOOL)isRTL
{
OWSAssert(string);
if (isRTL) {
return [string stringByAppendingString:self]; return [string stringByAppendingString:self];
} else { } else {
return [self stringByAppendingString:string]; return [self stringByAppendingString:string];
@ -28,7 +35,8 @@ NS_ASSUME_NONNULL_BEGIN
- (NSString *)removeAllCharactersIn:(NSCharacterSet *)characterSet - (NSString *)removeAllCharactersIn:(NSCharacterSet *)characterSet
{ {
OWSAssert(characterSet != nil); OWSAssert(characterSet);
return [[self componentsSeparatedByCharactersInSet:characterSet] componentsJoinedByString:@""]; return [[self componentsSeparatedByCharactersInSet:characterSet] componentsJoinedByString:@""];
} }

Loading…
Cancel
Save