Show date break header by hour instead of by day

Also ditch relative timestamps in favor of absolute ones
This commit is contained in:
Niels Andriesse 2021-07-08 14:30:54 +10:00
parent 356dc0cc4f
commit 28ea914097
6 changed files with 86 additions and 28 deletions

View file

@ -20,7 +20,8 @@ final class ContextMenuVC : UIViewController {
private lazy var timestampLabel: UILabel = { private lazy var timestampLabel: UILabel = {
let result = UILabel() let result = UILabel()
result.text = DateUtil.formatTimestamp(asTime: viewItem.interaction.timestampForUI()) let date = viewItem.interaction.dateForUI()
result.text = DateUtil.formatDate(forDisplay: date)
result.font = .systemFont(ofSize: Values.verySmallFontSize) result.font = .systemFont(ofSize: Values.verySmallFontSize)
result.textColor = isLightMode ? .black : .white result.textColor = isLightMode ? .black : .white
return result return result

View file

@ -1145,7 +1145,7 @@ NS_ASSUME_NONNULL_BEGIN
BOOL shouldShowDate = NO; BOOL shouldShowDate = NO;
if (previousViewItemTimestamp == 0) { if (previousViewItemTimestamp == 0) {
shouldShowDateOnNextViewItem = YES; shouldShowDateOnNextViewItem = YES;
} else if (![DateUtil isSameDayWithTimestamp:previousViewItemTimestamp timestamp:viewItemTimestamp]) { } else if (![DateUtil isSameHourWithTimestamp:previousViewItemTimestamp timestamp:viewItemTimestamp]) {
shouldShowDateOnNextViewItem = YES; shouldShowDateOnNextViewItem = YES;
} }

View file

@ -284,7 +284,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
dateBreakLabel.textColor = Colors.text dateBreakLabel.textColor = Colors.text
dateBreakLabel.textAlignment = .center dateBreakLabel.textAlignment = .center
let date = viewItem.interaction.dateForUI() let date = viewItem.interaction.dateForUI()
let description = DateUtil.formatDate(forConversationDateBreaks: date) let description = DateUtil.formatDate(forDisplay: date)
dateBreakLabel.text = description dateBreakLabel.text = description
headerView.addSubview(dateBreakLabel) headerView.addSubview(dateBreakLabel)
dateBreakLabel.pin(.top, to: .top, of: headerView, withInset: Values.smallSpacing) dateBreakLabel.pin(.top, to: .top, of: headerView, withInset: Values.smallSpacing)

View file

@ -179,7 +179,7 @@ final class ConversationCell : UITableViewCell {
unreadCountLabel.font = .boldSystemFont(ofSize: fontSize) unreadCountLabel.font = .boldSystemFont(ofSize: fontSize)
profilePictureView.update(for: thread) profilePictureView.update(for: thread)
displayNameLabel.text = getDisplayName() displayNameLabel.text = getDisplayName()
timestampLabel.text = DateUtil.formatDateShort(threadViewModel.lastMessageDate) timestampLabel.text = DateUtil.formatDate(forDisplay: threadViewModel.lastMessageDate)
if SSKEnvironment.shared.typingIndicators.typingRecipientId(forThread: thread) != nil { if SSKEnvironment.shared.typingIndicators.typingRecipientId(forThread: thread) != nil {
snippetLabel.text = "" snippetLabel.text = ""
typingIndicatorView.isHidden = false typingIndicatorView.isHidden = false

View file

@ -34,11 +34,14 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)exemplaryNowTimeFormat; + (NSString *)exemplaryNowTimeFormat;
+ (NSString *)exemplaryMinutesTimeFormat; + (NSString *)exemplaryMinutesTimeFormat;
+ (NSString *)formatDateForConversationDateBreaks:(NSDate *)date; + (NSString *)formatDateForDisplay:(NSDate *)date;
+ (BOOL)isSameDayWithTimestamp:(uint64_t)timestamp1 timestamp:(uint64_t)timestamp2; + (BOOL)isSameDayWithTimestamp:(uint64_t)timestamp1 timestamp:(uint64_t)timestamp2;
+ (BOOL)isSameDayWithDate:(NSDate *)date1 date:(NSDate *)date2; + (BOOL)isSameDayWithDate:(NSDate *)date1 date:(NSDate *)date2;
+ (BOOL)isSameHourWithTimestamp:(uint64_t)timestamp1 timestamp:(uint64_t)timestamp2;
+ (BOOL)isSameHourWithDate:(NSDate *)date1 date:(NSDate *)date2;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View file

@ -13,6 +13,13 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
@implementation DateUtil @implementation DateUtil
+ (NSString *)getHourFormat {
NSString *format = [NSDateFormatter dateFormatFromTemplate:@"j" options:0 locale:[NSLocale currentLocale]];
NSRange range = [format rangeOfString:@"a"];
BOOL is12HourTime = (range.location != NSNotFound);
return (is12HourTime) ? @"h:mm a" : @"HH:mm";
}
+ (NSDateFormatter *)dateFormatter { + (NSDateFormatter *)dateFormatter {
static NSDateFormatter *formatter; static NSDateFormatter *formatter;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
@ -25,50 +32,49 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return formatter; return formatter;
} }
+ (NSDateFormatter *)dateBreakRelativeDateFormatter + (NSDateFormatter *)displayDateTodayFormatter
{ {
static NSDateFormatter *formatter; static NSDateFormatter *formatter;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new]; formatter = [NSDateFormatter new];
formatter.locale = [NSLocale currentLocale]; formatter.locale = [NSLocale currentLocale];
formatter.dateStyle = NSDateFormatterShortStyle; // 9:10 am
formatter.timeStyle = NSDateFormatterNoStyle; formatter.dateFormat = [self getHourFormat];
formatter.doesRelativeDateFormatting = YES;
}); });
return formatter; return formatter;
} }
+ (NSDateFormatter *)dateBreakThisWeekDateFormatter + (NSDateFormatter *)displayDateThisWeekDateFormatter
{ {
static NSDateFormatter *formatter; static NSDateFormatter *formatter;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new]; formatter = [NSDateFormatter new];
formatter.locale = [NSLocale currentLocale]; formatter.locale = [NSLocale currentLocale];
// "Monday", "Tuesday", etc. // Mon 11:36 pm
formatter.dateFormat = @"EEEE"; formatter.dateFormat = [NSString stringWithFormat:@"EEE %@", [self getHourFormat]];
}); });
return formatter; return formatter;
} }
+ (NSDateFormatter *)dateBreakThisYearDateFormatter + (NSDateFormatter *)displayDateThisYearDateFormatter
{ {
static NSDateFormatter *formatter; static NSDateFormatter *formatter;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new]; formatter = [NSDateFormatter new];
formatter.locale = [NSLocale currentLocale]; formatter.locale = [NSLocale currentLocale];
// Tue, Jun 6 // Jun 6 10:12 am
formatter.dateFormat = @"EE, MMM d"; formatter.dateFormat = [NSString stringWithFormat:@"MMM d %@", [self getHourFormat]];
}); });
return formatter; return formatter;
} }
+ (NSDateFormatter *)dateBreakOldDateFormatter + (NSDateFormatter *)displayDateOldDateFormatter
{ {
static NSDateFormatter *formatter; static NSDateFormatter *formatter;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
@ -76,7 +82,7 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
formatter = [NSDateFormatter new]; formatter = [NSDateFormatter new];
formatter.locale = [NSLocale currentLocale]; formatter.locale = [NSLocale currentLocale];
formatter.dateStyle = NSDateFormatterMediumStyle; formatter.dateStyle = NSDateFormatterMediumStyle;
formatter.timeStyle = NSDateFormatterNoStyle; formatter.timeStyle = NSDateFormatterShortStyle;
formatter.doesRelativeDateFormatting = YES; formatter.doesRelativeDateFormatting = YES;
}); });
@ -130,6 +136,12 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return formatter; return formatter;
} }
+ (BOOL)isWithinOneMinute:(NSDate *)date
{
NSTimeInterval interval = [[NSDate new] timeIntervalSince1970] - [date timeIntervalSince1970];
return interval < 60;
}
+ (BOOL)dateIsOlderThanToday:(NSDate *)date + (BOOL)dateIsOlderThanToday:(NSDate *)date
{ {
return [self dateIsOlderThanToday:date now:[NSDate date]]; return [self dateIsOlderThanToday:date now:[NSDate date]];
@ -174,6 +186,18 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return dayDifference == 0; return dayDifference == 0;
} }
+ (BOOL)dateIsThisWeek:(NSDate *)date
{
return [self dateIsThisWeek:date now:[NSDate date]];
}
+ (BOOL)dateIsThisWeek:(NSDate *)date now:(NSDate *)now
{
NSCalendar *calendar = [NSCalendar currentCalendar];
return (
[calendar component:NSCalendarUnitWeekOfYear fromDate:date] == [calendar component:NSCalendarUnitWeekOfYear fromDate:now]);
}
+ (BOOL)dateIsThisYear:(NSDate *)date + (BOOL)dateIsThisYear:(NSDate *)date
{ {
return [self dateIsThisYear:date now:[NSDate date]]; return [self dateIsThisYear:date now:[NSDate date]];
@ -197,6 +221,22 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return dayDifference == 1; return dayDifference == 1;
} }
// Returns the difference in hours, ignoring minutes, seconds.
// If both dates are the same date, returns 0.
// If firstDate is an hour before secondDate, returns 1.
//
// Note: Assumes both dates use the "current" calendar.
+ (NSInteger)hoursFromFirstDate:(NSDate *)firstDate toSecondDate:(NSDate *)secondDate
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSCalendarUnit units = NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour;
NSDateComponents *comp1 = [calendar components:units fromDate:firstDate];
NSDateComponents *comp2 = [calendar components:units fromDate:secondDate];
NSDate *date1 = [calendar dateFromComponents:comp1];
NSDate *date2 = [calendar dateFromComponents:comp2];
return [[calendar components:NSCalendarUnitHour fromDate:date1 toDate:date2 options:0] hour];
}
// Returns the difference in days, ignoring hours, minutes, seconds. // Returns the difference in days, ignoring hours, minutes, seconds.
// If both dates are the same date, returns 0. // If both dates are the same date, returns 0.
// If firstDate is a day before secondDate, returns 1. // If firstDate is a day before secondDate, returns 1.
@ -281,22 +321,24 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return dateTimeString.localizedUppercaseString; return dateTimeString.localizedUppercaseString;
} }
+ (NSString *)formatDateForConversationDateBreaks:(NSDate *)date + (NSString *)formatDateForDisplay:(NSDate *)date
{ {
OWSAssertDebug(date); OWSAssertDebug(date);
if (![self dateIsThisYear:date]) { if (![self dateIsThisYear:date]) {
// last year formatter: Nov 11, 2017 // last year formatter: Nov 11 13:32 am, 2017
return [self.dateBreakOldDateFormatter stringFromDate:date]; return [self.displayDateOldDateFormatter stringFromDate:date];
} else if ([self dateIsOlderThanOneWeek:date]) { } else if (![self dateIsThisWeek:date]) {
// this year formatter: Tue, Jun 23 // this year formatter: Jun 6 10:12 am
return [self.dateBreakThisYearDateFormatter stringFromDate:date]; return [self.displayDateThisYearDateFormatter stringFromDate:date];
} else if ([self dateIsOlderThanYesterday:date]) { } else if (![self dateIsToday:date]) {
// day of week formatter: Thursday // day of week formatter: Thu 9:11 pm
return [self.dateBreakThisWeekDateFormatter stringFromDate:date]; return [self.displayDateThisWeekDateFormatter stringFromDate:date];
} else if (![self isWithinOneMinute:date]) {
// today formatter: 8:32 am
return [self.displayDateTodayFormatter stringFromDate:date];
} else { } else {
// relative format: Today / Yesterday return NSLocalizedString(@"DATE_NOW", @"");
return [self.dateBreakRelativeDateFormatter stringFromDate:date];
} }
} }
@ -443,6 +485,18 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return dayDifference == 0; return dayDifference == 0;
} }
+ (BOOL)isSameHourWithTimestamp:(uint64_t)timestamp1 timestamp:(uint64_t)timestamp2
{
return [self isSameHourWithDate:[NSDate ows_dateWithMillisecondsSince1970:timestamp1]
date:[NSDate ows_dateWithMillisecondsSince1970:timestamp2]];
}
+ (BOOL)isSameHourWithDate:(NSDate *)date1 date:(NSDate *)date2
{
NSInteger hourDifference = [self hoursFromFirstDate:date1 toSecondDate:date2];
return hourDifference == 0;
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END