Show the date on a room when the last message is older than today. (#484)

* Format the last message date correctly.
* Update room cell layout - fixes an issue where the longer the date got the smaller the last message width was.
This commit is contained in:
Doug
2023-01-24 11:02:51 +00:00
committed by GitHub
parent c34fb7e9c5
commit 3c2d2e7731
8 changed files with 197 additions and 69 deletions

View File

@@ -84,6 +84,7 @@
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */; };
23B2CD5A06B16055BDDD0994 /* ApplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44D8C8431416EB8DFEC7E235 /* ApplicationTests.swift */; };
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CF12478983A5EB390FB26 /* MessageComposer.swift */; };
24A75F72EEB7561B82D726FD /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; };
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; };
25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */; };
2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
@@ -391,6 +392,7 @@
C55A44C99F64A479ABA85B46 /* RoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */; };
C58E305C380D3ADDF7912180 /* StickerRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818695BED971753243FEF897 /* StickerRoomTimelineItem.swift */; };
C6136E848E55D2C86BF760F5 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C789E7BFC066CF39B8AE0974 /* NetworkMonitor.swift */; };
C6C06DDA8881260303FBA3A0 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; };
C74EE50257ED925C2B8EFCE6 /* MockSoftLogoutScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B869438A1B52836F912A702 /* MockSoftLogoutScreenState.swift */; };
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; };
C7B251DC896C0867C51B616D /* AnalyticsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541542F5AC323709D8563458 /* AnalyticsPrompt.swift */; };
@@ -400,8 +402,10 @@
CB498F4E27AA0545DCEF0F6F /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4003BC24B24C9E63D3304177 /* DeviceKit */; };
CB6BCBF28E4B76EA08C2926D /* StateRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */; };
CB99B0FA38A4AC596F38CC13 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */; };
CC736DA1AA8F8B9FD8785009 /* ScreenshotDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */; };
CCAA0671B46EAFD0BB528E2C /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 8FC26871038FB0E4AAE22605 /* apple_emojis_data.json */; };
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5E97E9615A158C76B2AB77 /* DateTests.swift */; };
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */; };
CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; };
CE7148E80F09B7305E026AC6 /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1198B925F4A88DA74083662 /* OnboardingViewModel.swift */; };
@@ -605,6 +609,7 @@
201305507D7DFD16E544563A /* EmojiLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLoaderProtocol.swift; sourceTree = "<group>"; };
2069C264213B9F381DF9F876 /* ta */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ta; path = ta.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
2112A6CFEA46E672D90EBF54 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/Localizable.strings; sourceTree = "<group>"; };
2141693488CE5446BB391964 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; };
21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
@@ -657,6 +662,7 @@
39EBB6903EFD4236B8D11A42 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-CA"; path = "fr-CA.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = "<group>"; };
3B5B535DA49C54523FF7A412 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nn; path = nn.lproj/Localizable.strings; sourceTree = "<group>"; };
3B5E97E9615A158C76B2AB77 /* DateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTests.swift; sourceTree = "<group>"; };
3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenUITests.swift; sourceTree = "<group>"; };
3C1A3D524D63815B28FA4D62 /* EmojiCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCategory.swift; sourceTree = "<group>"; };
3CDF9E55650D6035D6536538 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "nb-NO"; path = "nb-NO.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
@@ -897,6 +903,7 @@
AE225C66978648AA4AF37B45 /* te */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = te; path = te.lproj/Localizable.strings; sourceTree = "<group>"; };
AE5DDBEBBA17973ED4638823 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = de; path = de.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
AEC96B3DC55090BBF8876CC2 /* MockFileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFileCache.swift; sourceTree = "<group>"; };
AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilderTests.swift; sourceTree = "<group>"; };
AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptUITests.swift; sourceTree = "<group>"; };
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderTests.swift; sourceTree = "<group>"; };
B07B937B036247F1962BBCC7 /* RoomMemberDetailsMemberCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsMemberCell.swift; sourceTree = "<group>"; };
@@ -1423,6 +1430,7 @@
children = (
B6E89E530A8E92EC44301CA1 /* Bundle.swift */,
A9FAFE1C2149E6AC8156ED2B /* Collection.swift */,
2141693488CE5446BB391964 /* Date.swift */,
04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */,
E26747B3154A5DBC3A7E24A5 /* Image.swift */,
4E2245243369B99216C7D84E /* ImageCache.swift */,
@@ -1686,6 +1694,7 @@
6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */,
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */,
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */,
3B5E97E9615A158C76B2AB77 /* DateTests.swift */,
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */,
9BF9E3E6A23180EC05F06460 /* EmojiMartJSONLoaderTests.swift */,
099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */,
@@ -1705,6 +1714,7 @@
2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */,
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */,
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */,
AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */,
F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */,
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */,
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */,
@@ -2870,6 +2880,7 @@
1B4B3E847BF944DB2C1C217F /* BackgroundTaskServiceProtocol.swift in Sources */,
9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */,
DFCA89C4EC2A5332ED6B441F /* DataProtectionManager.swift in Sources */,
24A75F72EEB7561B82D726FD /* Date.swift in Sources */,
E8AB8D16E6D8E8E501F29BD9 /* FileCache.swift in Sources */,
A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */,
59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */,
@@ -2910,6 +2921,7 @@
0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */,
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */,
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */,
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */,
9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */,
501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */,
25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */,
@@ -2937,6 +2949,7 @@
EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */,
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */,
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */,
CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */,
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */,
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */,
86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */,
@@ -3008,6 +3021,7 @@
C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */,
12C867E85E6D12EEDFD0B127 /* CustomStringConvertible.swift in Sources */,
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */,
C6C06DDA8881260303FBA3A0 /* Date.swift in Sources */,
1CF18DE71D5D23C61BD88852 /* DebugScreen.swift in Sources */,
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */,
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */,

View File

@@ -0,0 +1,43 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
extension Date {
/// The date formatted with the minimal necessary units given how long ago it occurred.
func formattedMinimal() -> String {
let calendar = Calendar.current
if calendar.isDateInToday(self) {
// Just the time if it was today.
return formatted(date: .omitted, time: .shortened)
} else if calendar.isDateInYesterday(self) {
// Simply "Yesterday" if it was yesterday.
return formatted(Date.RelativeFormatStyle(presentation: .named, capitalizationContext: .beginningOfSentence))
} else if let sixDaysAgo = calendar.date(byAdding: .day, value: -6, to: calendar.startOfDay(for: .now)),
sixDaysAgo <= self {
// The named day if it was in the last 6 days.
return formatted(.dateTime.weekday(.wide))
} else if let oneYearAgo = calendar.date(byAdding: .year, value: -1, to: .now),
oneYearAgo <= self {
// The day and month if it was in the last 6 days.
return formatted(.dateTime.day().month())
} else {
// The day, month and year if it is any older.
return formatted(.dateTime.year().day().month())
}
}
}

View File

@@ -261,7 +261,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
}
if let lastMessageTimestamp = details.lastMessageTimestamp {
room.timestamp = lastMessageTimestamp.formatted(date: .omitted, time: .shortened)
room.timestamp = lastMessageTimestamp.formattedMinimal()
}
roomsForIdentifiers[details.id] = room

View File

@@ -35,16 +35,15 @@ struct HomeScreen: View {
}
if context.viewState.roomListMode == .skeletons {
LazyVStack {
LazyVStack(spacing: 0) {
ForEach(context.viewState.visibleRooms) { room in
HomeScreenRoomCell(room: room, context: context)
.redacted(reason: .placeholder)
.disabled(true)
}
}
.padding(.horizontal)
} else {
LazyVStack {
LazyVStack(spacing: 0) {
ForEach(context.viewState.visibleRooms) { room in
Group {
if room.isPlaceholder {
@@ -62,7 +61,6 @@ struct HomeScreen: View {
}
}
}
.padding(.horizontal)
.searchable(text: $context.searchQuery)
.disableAutocorrection(true)
}

View File

@@ -29,60 +29,11 @@ struct HomeScreenRoomCell: View {
}
} label: {
HStack(spacing: 16.0) {
if let avatar = room.avatar {
Image(uiImage: avatar)
.resizable()
.scaledToFill()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
.accessibilityHidden(true)
} else {
PlaceholderAvatarImage(text: room.name, contentId: room.roomId)
.clipShape(Circle())
.frame(width: avatarSize, height: avatarSize)
.accessibilityHidden(true)
}
avatar
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 2.0) {
Text(room.name)
.font(.element.callout.bold())
.foregroundColor(.element.primaryContent)
.lineLimit(1)
if let lastMessage = room.lastMessage, !String(lastMessage.characters).isEmpty {
Text(lastMessage)
.font(lastMessageFont)
.foregroundColor(lastMessageForegroundColor)
.lineLimit(2)
.multilineTextAlignment(.leading)
.padding(.top, 2)
.id(lastMessage)
.transition(.opacity.animation(.elementDefault))
}
}
.animation(.elementDefault, value: room)
Spacer()
VStack(alignment: .trailing, spacing: 3.0) {
if let timestamp = room.timestamp {
Text(timestamp)
.font(.element.caption1)
.foregroundColor(.element.secondaryContent)
.id(timestamp)
.transition(.opacity.animation(.elementDefault))
}
if room.hasUnreads {
Rectangle()
.frame(width: 12, height: 12)
.foregroundColor(.element.primaryContent)
.clipShape(Circle())
.transition(.opacity.animation(.elementDefault))
}
}
.animation(.elementDefault, value: room)
VStack(alignment: .leading, spacing: 2) {
header
footer
}
}
.frame(minHeight: 64.0)
@@ -93,23 +44,99 @@ struct HomeScreenRoomCell: View {
}
}
}
.buttonStyle(HomeScreenRoomCellButtonStyle())
.accessibilityIdentifier("roomName:\(room.name)")
}
var lastMessageFont: Font {
if room.hasUnreads {
return .element.subheadline.bold()
@ViewBuilder
var avatar: some View {
if let avatar = room.avatar {
Image(uiImage: avatar)
.resizable()
.scaledToFill()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
.accessibilityHidden(true)
} else {
return .element.subheadline
PlaceholderAvatarImage(text: room.name, contentId: room.roomId)
.clipShape(Circle())
.frame(width: avatarSize, height: avatarSize)
.accessibilityHidden(true)
}
}
var lastMessageForegroundColor: Color {
if room.hasUnreads {
return .element.primaryContent
} else {
return .element.secondaryContent
@ViewBuilder
var header: some View {
HStack(alignment: .firstTextBaseline) {
Text(room.name)
.font(.element.callout.bold())
.foregroundColor(.element.primaryContent)
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)
if let timestamp = room.timestamp {
Text(timestamp)
.font(.element.caption1)
.foregroundColor(.element.secondaryContent)
.id(timestamp)
.transition(.opacity.animation(.elementDefault))
}
}
.animation(.elementDefault, value: room)
}
@ViewBuilder
var footer: some View {
HStack(alignment: .firstTextBaseline) {
ZStack(alignment: .topLeading) {
// Hidden text with 2 lines to maintain consistent height, scaling with dynamic text.
Text(" \n ").lastMessageFormatting().hidden()
if let lastMessage = room.lastMessage, !String(lastMessage.characters).isEmpty {
Text(lastMessage)
.lastMessageFormatting()
.id(lastMessage)
.transition(.opacity.animation(.elementDefault))
}
}
Spacer()
if room.hasUnreads {
Rectangle()
.frame(width: 12, height: 12)
.foregroundColor(.element.primaryContent)
.clipShape(Circle())
.transition(.opacity.animation(.elementDefault))
}
}
.animation(.elementDefault, value: room)
}
}
struct HomeScreenRoomCellButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.roomCellBackground(configuration.isPressed ? .element.system : .clear)
.contentShape(Rectangle())
}
}
private extension View {
func lastMessageFormatting() -> some View {
font(.element.subheadline)
.foregroundColor(.element.secondaryContent)
.lineLimit(2)
.multilineTextAlignment(.leading)
.padding(.top, 2)
}
// To be used to indicate the selected room too
func roomCellBackground(_ background: Color) -> some View {
padding(.horizontal, 8)
.padding(.vertical, 6)
.background { background.clipShape(RoundedRectangle(cornerRadius: 12)) }
.padding(.horizontal, 8)
}
}
@@ -136,11 +163,12 @@ struct HomeScreenRoomCell_Previews: PreviewProvider {
roomId: details.id,
name: details.name,
hasUnreads: details.unreadNotificationCount > 0,
timestamp: Date.now.formatted(date: .omitted, time: .shortened))
timestamp: Date.now.formattedMinimal(),
lastMessage: details.lastMessage)
}
}
return VStack {
return VStack(spacing: 0) {
ForEach(rooms) { room in
HomeScreenRoomCell(room: room, context: viewModel.context)
}

View File

@@ -88,6 +88,7 @@ targets:
- path: ../../ElementX/Sources/Other/Extensions/FileManager.swift
- path: ../../ElementX/Sources/Other/Extensions/URL.swift
- path: ../../ElementX/Sources/Other/Extensions/Bundle.swift
- path: ../../ElementX/Sources/Other/Extensions/Date.swift
- path: ../../ElementX/Sources/Other/Extensions/ImageCache.swift
- path: ../../ElementX/Sources/Other/AvatarSize.swift
- path: ../../ElementX/Sources/Other/InfoPlistReader.swift

View File

@@ -0,0 +1,43 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
@testable import ElementX
import XCTest
// swiftlint:disable force_unwrapping
class DateTests: XCTestCase {
let calendar = Calendar.current
let startOfToday = Calendar.current.startOfDay(for: .now)
let startOfYesterday = Calendar.current.startOfDay(for: Calendar.current.date(byAdding: .day, value: -1, to: .now)!)
func testMinimalDateFormatting() {
let today = calendar.date(byAdding: DateComponents(hour: 9, minute: 30), to: startOfToday)
XCTAssertEqual(today?.formattedMinimal(), "9:30 AM")
let yesterday = calendar.date(byAdding: .hour, value: 1, to: startOfYesterday)
XCTAssertEqual(yesterday?.formattedMinimal(), "Yesterday")
let saturday = calendar.nextWeekend(startingAfter: startOfToday, direction: .backward)?.start
XCTAssertEqual(saturday?.formattedMinimal(), "Saturday")
// This test will fail during the first 6 days of the year.
let newYearsDay = calendar.date(from: DateComponents(year: calendar.component(.year, from: startOfToday), month: 1, day: 1))!
XCTAssertEqual(newYearsDay.formattedMinimal(), "Jan 1")
let theMillennium = calendar.date(from: DateComponents(year: 2000, month: 1, day: 1))!
XCTAssertEqual(theMillennium.formattedMinimal(), "Jan 1, 2000")
}
}

View File

@@ -0,0 +1 @@
Show the date instead of the time in the room list when the last message is from yesterday or before.