From b1fbd2d8cd6405516b050ae4734112d0d6dae1dc Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Wed, 21 May 2025 14:12:53 +0300 Subject: [PATCH 1/2] Show time, relative date, weekday, or short date for last message in channel list and search --- CHANGELOG.md | 1 + .../ChatChannelList/ChatChannelListItem.swift | 2 +- .../ChatChannelList/SearchResultsView.swift | 2 +- Sources/StreamChatSwiftUI/Utils.swift | 3 + .../Common/ChannelListDateFormatter.swift | 75 +++++++++++++++++++ StreamChatSwiftUI.xcodeproj/project.pbxproj | 8 ++ .../ChannelListDateFormatter_Tests.swift | 61 +++++++++++++++ 7 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 Sources/StreamChatSwiftUI/Utils/Common/ChannelListDateFormatter.swift create mode 100644 StreamChatSwiftUITests/Tests/Utils/ChannelListDateFormatter_Tests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index ad7fc640c..0bbf462ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### ✅ Added - Add extra data to user display info [#819](https://github.com/GetStream/stream-chat-swiftui/pull/819) - Make message spacing in message list configurable [#830](https://github.com/GetStream/stream-chat-swiftui/pull/830) +- Show time, relative date, weekday, or short date for last message in channel list and search [#833](https://github.com/GetStream/stream-chat-swiftui/pull/833) ### 🐞 Fixed - Fix swipe to reply enabled when quoting a message is disabled [#824](https://github.com/GetStream/stream-chat-swiftui/pull/824) - Fix mark unread action not removed when read events are disabled [#823](https://github.com/GetStream/stream-chat-swiftui/pull/823) diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift index e36f5de9f..862a90f7d 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift @@ -337,7 +337,7 @@ extension ChatChannel { public var timestampText: String { if let lastMessageAt = lastMessageAt { - return InjectedValues[\.utils].dateFormatter.string(from: lastMessageAt) + return InjectedValues[\.utils].channelListDateFormatter.string(from: lastMessageAt) } else { return "" } diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift b/Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift index 455f2d8d0..c00153614 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift @@ -158,7 +158,7 @@ struct SearchResultItem: View { private var timestampText: String { if let lastMessageAt = searchResult.channel.lastMessageAt { - return utils.dateFormatter.string(from: lastMessageAt) + return utils.channelListDateFormatter.string(from: lastMessageAt) } else { return "" } diff --git a/Sources/StreamChatSwiftUI/Utils.swift b/Sources/StreamChatSwiftUI/Utils.swift index c36970e32..3ee6f09af 100644 --- a/Sources/StreamChatSwiftUI/Utils.swift +++ b/Sources/StreamChatSwiftUI/Utils.swift @@ -13,6 +13,7 @@ public class Utils { var markdownFormatter = MarkdownFormatter() public var dateFormatter: DateFormatter + public var channelListDateFormatter: DateFormatter public var videoPreviewLoader: VideoPreviewLoader public var imageLoader: ImageLoading public var imageCDN: ImageCDN @@ -69,6 +70,7 @@ public class Utils { public init( dateFormatter: DateFormatter = .makeDefault(), + channelListDateFormatter: DateFormatter = ChannelListDateFormatter(), videoPreviewLoader: VideoPreviewLoader = DefaultVideoPreviewLoader(), imageLoader: ImageLoading = NukeImageLoader(), imageCDN: ImageCDN = StreamImageCDN(), @@ -93,6 +95,7 @@ public class Utils { shouldSyncChannelControllerOnAppear: @escaping (ChatChannelController) -> Bool = { _ in true } ) { self.dateFormatter = dateFormatter + self.channelListDateFormatter = channelListDateFormatter self.videoPreviewLoader = videoPreviewLoader self.imageLoader = imageLoader self.imageCDN = imageCDN diff --git a/Sources/StreamChatSwiftUI/Utils/Common/ChannelListDateFormatter.swift b/Sources/StreamChatSwiftUI/Utils/Common/ChannelListDateFormatter.swift new file mode 100644 index 000000000..a060b9361 --- /dev/null +++ b/Sources/StreamChatSwiftUI/Utils/Common/ChannelListDateFormatter.swift @@ -0,0 +1,75 @@ +// +// Copyright © 2025 Stream.io Inc. All rights reserved. +// + +import Foundation + +/// A formatter that converts last message timestamps in the channel list. +/// +/// Shows time, relative date, weekday or short date based on days passed. +public final class ChannelListDateFormatter: DateFormatter, @unchecked Sendable { + override public init() { + super.init() + locale = .autoupdatingCurrent + dateStyle = .short + timeStyle = .none + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func string(from date: Date) -> String { + if calendar.isDateInToday(date) { + return time.string(from: date) + } + if calendar.isDateInYesterday(date) { + return shortRelativeDate.string(from: date) + } + if calendar.isDateInLastWeek(date) { + return weekday.string(from: date) + } + + return super.string(from: date) + } + + override public var locale: Locale! { + didSet { + [time, shortRelativeDate, weekday].forEach { $0.locale = locale } + } + } + + let time: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = .autoupdatingCurrent + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + let shortRelativeDate: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = .autoupdatingCurrent + formatter.dateStyle = .short + formatter.timeStyle = .none + formatter.doesRelativeDateFormatting = true + return formatter + }() + + let weekday: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = .autoupdatingCurrent + formatter.setLocalizedDateFormatFromTemplate("EEEE") + return formatter + }() +} + +extension Calendar { + func isDateInLastWeek(_ date: Date) -> Bool { + guard let dateBefore7days = self.date(byAdding: .day, value: -7, to: Date()) else { + return false + } + return date > dateBefore7days + } +} diff --git a/StreamChatSwiftUI.xcodeproj/project.pbxproj b/StreamChatSwiftUI.xcodeproj/project.pbxproj index 582fad6af..dc34edceb 100644 --- a/StreamChatSwiftUI.xcodeproj/project.pbxproj +++ b/StreamChatSwiftUI.xcodeproj/project.pbxproj @@ -16,10 +16,12 @@ 4F6D83352C0F05040098C298 /* PollCommentsViewModel_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83342C0F05040098C298 /* PollCommentsViewModel_Tests.swift */; }; 4F6D83512C1079A00098C298 /* AlertBannerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83502C1079A00098C298 /* AlertBannerViewModifier.swift */; }; 4F6D83542C1094220098C298 /* AlertBannerViewModifier_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83532C1094220098C298 /* AlertBannerViewModifier_Tests.swift */; }; + 4F7613792DDCB2C900F996E3 /* ChannelListDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7613782DDCB2AD00F996E3 /* ChannelListDateFormatter.swift */; }; 4F7720AE2C58C45200BAEC02 /* OnLoadViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7720AD2C58C45000BAEC02 /* OnLoadViewModifier.swift */; }; 4F7DD9A02BFC7C6100599AA6 /* ChatClient+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7DD99F2BFC7C6100599AA6 /* ChatClient+Extensions.swift */; }; 4F7DD9A22BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7DD9A12BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift */; }; 4F889C562D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F889C552D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift */; }; + 4F8D64402DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8D643F2DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift */; }; 4FA3741A2D799CA400294721 /* AppConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA374192D799CA400294721 /* AppConfigurationView.swift */; }; 4FA3741D2D799FC300294721 /* AppConfigurationTranslationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA3741C2D799FC300294721 /* AppConfigurationTranslationView.swift */; }; 4FA3741F2D79A64F00294721 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA3741E2D79A64900294721 /* AppConfiguration.swift */; }; @@ -612,10 +614,12 @@ 4F6D83342C0F05040098C298 /* PollCommentsViewModel_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollCommentsViewModel_Tests.swift; sourceTree = ""; }; 4F6D83502C1079A00098C298 /* AlertBannerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBannerViewModifier.swift; sourceTree = ""; }; 4F6D83532C1094220098C298 /* AlertBannerViewModifier_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBannerViewModifier_Tests.swift; sourceTree = ""; }; + 4F7613782DDCB2AD00F996E3 /* ChannelListDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListDateFormatter.swift; sourceTree = ""; }; 4F7720AD2C58C45000BAEC02 /* OnLoadViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnLoadViewModifier.swift; sourceTree = ""; }; 4F7DD99F2BFC7C6100599AA6 /* ChatClient+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatClient+Extensions.swift"; sourceTree = ""; }; 4F7DD9A12BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatClientExtensions_Tests.swift; sourceTree = ""; }; 4F889C552D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageExtensions_Tests.swift; sourceTree = ""; }; + 4F8D643F2DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListDateFormatter_Tests.swift; sourceTree = ""; }; 4FA374192D799CA400294721 /* AppConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationView.swift; sourceTree = ""; }; 4FA3741C2D799FC300294721 /* AppConfigurationTranslationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationTranslationView.swift; sourceTree = ""; }; 4FA3741E2D79A64900294721 /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; @@ -1924,6 +1928,7 @@ 4FCD7DBC2D633F6C000EEB0F /* AttributedString+Extensions.swift */, 8465FD392746A95600AF091E /* AutoLayoutHelpers.swift */, 8465FD452746A95600AF091E /* Cache.swift */, + 4F7613782DDCB2AD00F996E3 /* ChannelListDateFormatter.swift */, 8465FD412746A95600AF091E /* ChatChannelNamer.swift */, 4F7DD99F2BFC7C6100599AA6 /* ChatClient+Extensions.swift */, 8465FD442746A95600AF091E /* ChatMessage+Extensions.swift */, @@ -2180,6 +2185,7 @@ isa = PBXGroup; children = ( 84D6E52B2B3078D200D0056C /* AudioRecordingNameFormatter_Tests.swift */, + 4F8D643F2DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift */, 84C94D61275A5BB7007FE2B9 /* ChatChannelNamer_Tests.swift */, 4F7DD9A12BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift */, 4F889C552D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift */, @@ -2806,6 +2812,7 @@ 8465FDA52746A95700AF091E /* Modifiers.swift in Sources */, 8465FDBB2746A95700AF091E /* LoadingView.swift in Sources */, 84D6E4F62B2CA4E300D0056C /* RecordingTipView.swift in Sources */, + 4F7613792DDCB2C900F996E3 /* ChannelListDateFormatter.swift in Sources */, 846608E3278C303800D3D7B3 /* TypingIndicatorView.swift in Sources */, 84A1CACF2816BCF00046595A /* AddUsersView.swift in Sources */, 82D64BF02AD7E5B700C5C79E /* DataLoading.swift in Sources */, @@ -3065,6 +3072,7 @@ 84B2B5CA281947E100479CEE /* ViewFrameUtils.swift in Sources */, 8423C342277CBA280092DCF1 /* TypingSuggester_Tests.swift in Sources */, 84507C9A281ACCD70081DDC2 /* AddUsersView_Tests.swift in Sources */, + 4F8D64402DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift in Sources */, 84C94D0627578BF2007FE2B9 /* UnwrapAsync.swift in Sources */, 84D6B55A27DF6EC7009C6D07 /* LoadingView_Tests.swift in Sources */, 84C94D0427578BF2007FE2B9 /* TestError.swift in Sources */, diff --git a/StreamChatSwiftUITests/Tests/Utils/ChannelListDateFormatter_Tests.swift b/StreamChatSwiftUITests/Tests/Utils/ChannelListDateFormatter_Tests.swift new file mode 100644 index 000000000..c646287df --- /dev/null +++ b/StreamChatSwiftUITests/Tests/Utils/ChannelListDateFormatter_Tests.swift @@ -0,0 +1,61 @@ +// +// Copyright © 2025 Stream.io Inc. All rights reserved. +// + +import Foundation +@testable import StreamChat +@testable import StreamChatSwiftUI +import XCTest + +final class ChannelListDateFormatter_Tests: StreamChatTestCase { + private var formatter: ChannelListDateFormatter! + + override func setUpWithError() throws { + try super.setUpWithError() + formatter = ChannelListDateFormatter() + formatter.locale = Locale(identifier: "en_UK") + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + formatter = nil + } + + func test_showingTimeOnly() throws { + let date = try XCTUnwrap(Calendar.current.date(bySettingHour: 1, minute: 2, second: 3, of: Date())) + let result = formatter.string(from: date) + let expected = formatter.time.string(from: date) + XCTAssertEqual(expected, result) + XCTAssertEqual("01:02", result) + } + + func test_showingYesterday() throws { + let date = try XCTUnwrap(Calendar.current.date(byAdding: .day, value: -1, to: Date())) + let result = formatter.string(from: date) + let expected = formatter.shortRelativeDate.string(from: date) + XCTAssertEqual(expected, result) + XCTAssertEqual("Yesterday", result) + } + + func test_showingWeekday() throws { + let date = try XCTUnwrap(Calendar.current.date(byAdding: .day, value: -6, to: Date())) + let result = formatter.string(from: date) + let expected = formatter.weekday.string(from: date) + XCTAssertEqual(expected, result) + } + + func test_showingShortDate() throws { + let components = DateComponents( + timeZone: TimeZone(secondsFromGMT: 0), + year: 2025, + month: 1, + day: 15, + hour: 3, + minute: 4, + second: 5 + ) + let date = try XCTUnwrap(Calendar.current.date(from: components)) + let result = formatter.string(from: date) + XCTAssertEqual("15/01/2025", result) + } +} From 9d26265819a2b3a290ad22e988b819a410e1965a Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Thu, 22 May 2025 10:34:48 +0300 Subject: [PATCH 2/2] Add ChannelListConfig for relative dates --- CHANGELOG.md | 1 + DemoAppSwiftUI/AppDelegate.swift | 3 ++ .../ChatChannelList/ChannelListConfig.swift | 17 +++++++++ .../ChatChannelList/ChatChannelListItem.swift | 6 ++- .../ChatChannelList/SearchResultsView.swift | 5 ++- Sources/StreamChatSwiftUI/Utils.swift | 13 +++++-- ...ift => MessageRelativeDateFormatter.swift} | 32 +++++----------- StreamChatSwiftUI.xcodeproj/project.pbxproj | 38 ++++++++++--------- ... MessageRelativeDateFormatter_Tests.swift} | 22 ++++++----- 9 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 Sources/StreamChatSwiftUI/ChatChannelList/ChannelListConfig.swift rename Sources/StreamChatSwiftUI/Utils/Common/{ChannelListDateFormatter.swift => MessageRelativeDateFormatter.swift} (61%) rename StreamChatSwiftUITests/Tests/Utils/{ChannelListDateFormatter_Tests.swift => MessageRelativeDateFormatter_Tests.swift} (70%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bbf462ad..5cbeedb44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add extra data to user display info [#819](https://github.com/GetStream/stream-chat-swiftui/pull/819) - Make message spacing in message list configurable [#830](https://github.com/GetStream/stream-chat-swiftui/pull/830) - Show time, relative date, weekday, or short date for last message in channel list and search [#833](https://github.com/GetStream/stream-chat-swiftui/pull/833) + - Set `ChannelListConfig.messageRelativeDateFormatEnabled` to true for enabling it ### 🐞 Fixed - Fix swipe to reply enabled when quoting a message is disabled [#824](https://github.com/GetStream/stream-chat-swiftui/pull/824) - Fix mark unread action not removed when read events are disabled [#823](https://github.com/GetStream/stream-chat-swiftui/pull/823) diff --git a/DemoAppSwiftUI/AppDelegate.swift b/DemoAppSwiftUI/AppDelegate.swift index e0c1e5bf0..df66cbe1a 100644 --- a/DemoAppSwiftUI/AppDelegate.swift +++ b/DemoAppSwiftUI/AppDelegate.swift @@ -63,6 +63,9 @@ class AppDelegate: NSObject, UIApplicationDelegate { #endif let utils = Utils( + channelListConfig: ChannelListConfig( + messageRelativeDateFormatEnabled: true + ), messageListConfig: MessageListConfig( dateIndicatorPlacement: .messageList, userBlockingEnabled: true, diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChannelListConfig.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChannelListConfig.swift new file mode 100644 index 000000000..6bd0aa495 --- /dev/null +++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChannelListConfig.swift @@ -0,0 +1,17 @@ +// +// Copyright © 2025 Stream.io Inc. All rights reserved. +// + +import Foundation + +/// A configuration for channel lists. +public struct ChannelListConfig { + public init(messageRelativeDateFormatEnabled: Bool = false) { + self.messageRelativeDateFormatEnabled = messageRelativeDateFormatEnabled + } + + /// If true, the timestamp format depends on the time passed. + /// + /// Different date formats are used for today, yesterday, last 7 days, and older dates. + public var messageRelativeDateFormatEnabled: Bool +} diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift index 862a90f7d..cd14fe253 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift @@ -337,7 +337,11 @@ extension ChatChannel { public var timestampText: String { if let lastMessageAt = lastMessageAt { - return InjectedValues[\.utils].channelListDateFormatter.string(from: lastMessageAt) + let utils = InjectedValues[\.utils] + let formatter = utils.channelListConfig.messageRelativeDateFormatEnabled ? + utils.messageRelativeDateFormatter : + utils.dateFormatter + return formatter.string(from: lastMessageAt) } else { return "" } diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift b/Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift index c00153614..ab4e6750a 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift @@ -158,7 +158,10 @@ struct SearchResultItem: View { private var timestampText: String { if let lastMessageAt = searchResult.channel.lastMessageAt { - return utils.channelListDateFormatter.string(from: lastMessageAt) + let formatter = utils.channelListConfig.messageRelativeDateFormatEnabled ? + utils.messageRelativeDateFormatter : + utils.dateFormatter + return formatter.string(from: lastMessageAt) } else { return "" } diff --git a/Sources/StreamChatSwiftUI/Utils.swift b/Sources/StreamChatSwiftUI/Utils.swift index 3ee6f09af..37a3ddd9b 100644 --- a/Sources/StreamChatSwiftUI/Utils.swift +++ b/Sources/StreamChatSwiftUI/Utils.swift @@ -13,7 +13,11 @@ public class Utils { var markdownFormatter = MarkdownFormatter() public var dateFormatter: DateFormatter - public var channelListDateFormatter: DateFormatter + + /// Date formatter where the format depends on the time passed. + /// + /// - SeeAlso: ``ChannelListConfig/messageRelativeDateFormatEnabled``. + public var messageRelativeDateFormatter: DateFormatter public var videoPreviewLoader: VideoPreviewLoader public var imageLoader: ImageLoading public var imageCDN: ImageCDN @@ -26,6 +30,7 @@ public class Utils { public var messageTypeResolver: MessageTypeResolving public var messageActionsResolver: MessageActionsResolving public var commandsConfig: CommandsConfig + public var channelListConfig: ChannelListConfig public var messageListConfig: MessageListConfig public var composerConfig: ComposerConfig public var pollsConfig: PollsConfig @@ -70,7 +75,7 @@ public class Utils { public init( dateFormatter: DateFormatter = .makeDefault(), - channelListDateFormatter: DateFormatter = ChannelListDateFormatter(), + messageRelativeDateFormatter: DateFormatter = MessageRelativeDateFormatter(), videoPreviewLoader: VideoPreviewLoader = DefaultVideoPreviewLoader(), imageLoader: ImageLoading = NukeImageLoader(), imageCDN: ImageCDN = StreamImageCDN(), @@ -81,6 +86,7 @@ public class Utils { messageTypeResolver: MessageTypeResolving = MessageTypeResolver(), messageActionResolver: MessageActionsResolving = MessageActionsResolver(), commandsConfig: CommandsConfig = DefaultCommandsConfig(), + channelListConfig: ChannelListConfig = ChannelListConfig(), messageListConfig: MessageListConfig = MessageListConfig(), composerConfig: ComposerConfig = ComposerConfig(), pollsConfig: PollsConfig = PollsConfig(), @@ -95,7 +101,7 @@ public class Utils { shouldSyncChannelControllerOnAppear: @escaping (ChatChannelController) -> Bool = { _ in true } ) { self.dateFormatter = dateFormatter - self.channelListDateFormatter = channelListDateFormatter + self.messageRelativeDateFormatter = messageRelativeDateFormatter self.videoPreviewLoader = videoPreviewLoader self.imageLoader = imageLoader self.imageCDN = imageCDN @@ -108,6 +114,7 @@ public class Utils { self.messageTypeResolver = messageTypeResolver messageActionsResolver = messageActionResolver self.commandsConfig = commandsConfig + self.channelListConfig = channelListConfig self.messageListConfig = messageListConfig self.composerConfig = composerConfig self.snapshotCreator = snapshotCreator diff --git a/Sources/StreamChatSwiftUI/Utils/Common/ChannelListDateFormatter.swift b/Sources/StreamChatSwiftUI/Utils/Common/MessageRelativeDateFormatter.swift similarity index 61% rename from Sources/StreamChatSwiftUI/Utils/Common/ChannelListDateFormatter.swift rename to Sources/StreamChatSwiftUI/Utils/Common/MessageRelativeDateFormatter.swift index a060b9361..8c081dbf0 100644 --- a/Sources/StreamChatSwiftUI/Utils/Common/ChannelListDateFormatter.swift +++ b/Sources/StreamChatSwiftUI/Utils/Common/MessageRelativeDateFormatter.swift @@ -4,10 +4,8 @@ import Foundation -/// A formatter that converts last message timestamps in the channel list. -/// -/// Shows time, relative date, weekday or short date based on days passed. -public final class ChannelListDateFormatter: DateFormatter, @unchecked Sendable { +/// A formatter that converts message timestamps to a format which depends on the time passed. +public final class MessageRelativeDateFormatter: DateFormatter, @unchecked Sendable { override public init() { super.init() locale = .autoupdatingCurrent @@ -22,33 +20,23 @@ public final class ChannelListDateFormatter: DateFormatter, @unchecked Sendable override public func string(from date: Date) -> String { if calendar.isDateInToday(date) { - return time.string(from: date) + return todayFormatter.string(from: date) } if calendar.isDateInYesterday(date) { - return shortRelativeDate.string(from: date) + return yesterdayFormatter.string(from: date) } if calendar.isDateInLastWeek(date) { - return weekday.string(from: date) + return weekdayFormatter.string(from: date) } return super.string(from: date) } - - override public var locale: Locale! { - didSet { - [time, shortRelativeDate, weekday].forEach { $0.locale = locale } - } + + var todayFormatter: DateFormatter { + InjectedValues[\.utils].dateFormatter } - let time: DateFormatter = { - let formatter = DateFormatter() - formatter.locale = .autoupdatingCurrent - formatter.dateStyle = .none - formatter.timeStyle = .short - return formatter - }() - - let shortRelativeDate: DateFormatter = { + let yesterdayFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = .autoupdatingCurrent formatter.dateStyle = .short @@ -57,7 +45,7 @@ public final class ChannelListDateFormatter: DateFormatter, @unchecked Sendable return formatter }() - let weekday: DateFormatter = { + let weekdayFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = .autoupdatingCurrent formatter.setLocalizedDateFormatFromTemplate("EEEE") diff --git a/StreamChatSwiftUI.xcodeproj/project.pbxproj b/StreamChatSwiftUI.xcodeproj/project.pbxproj index dc34edceb..2eac3e556 100644 --- a/StreamChatSwiftUI.xcodeproj/project.pbxproj +++ b/StreamChatSwiftUI.xcodeproj/project.pbxproj @@ -16,12 +16,13 @@ 4F6D83352C0F05040098C298 /* PollCommentsViewModel_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83342C0F05040098C298 /* PollCommentsViewModel_Tests.swift */; }; 4F6D83512C1079A00098C298 /* AlertBannerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83502C1079A00098C298 /* AlertBannerViewModifier.swift */; }; 4F6D83542C1094220098C298 /* AlertBannerViewModifier_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83532C1094220098C298 /* AlertBannerViewModifier_Tests.swift */; }; - 4F7613792DDCB2C900F996E3 /* ChannelListDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7613782DDCB2AD00F996E3 /* ChannelListDateFormatter.swift */; }; + 4F7613792DDCB2C900F996E3 /* MessageRelativeDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7613782DDCB2AD00F996E3 /* MessageRelativeDateFormatter.swift */; }; 4F7720AE2C58C45200BAEC02 /* OnLoadViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7720AD2C58C45000BAEC02 /* OnLoadViewModifier.swift */; }; 4F7DD9A02BFC7C6100599AA6 /* ChatClient+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7DD99F2BFC7C6100599AA6 /* ChatClient+Extensions.swift */; }; 4F7DD9A22BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7DD9A12BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift */; }; 4F889C562D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F889C552D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift */; }; - 4F8D64402DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8D643F2DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift */; }; + 4F8D64402DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8D643F2DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift */; }; + 4F9173FD2DDDFFE8003C30B5 /* ChannelListConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F9173FC2DDDFFE3003C30B5 /* ChannelListConfig.swift */; }; 4FA3741A2D799CA400294721 /* AppConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA374192D799CA400294721 /* AppConfigurationView.swift */; }; 4FA3741D2D799FC300294721 /* AppConfigurationTranslationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA3741C2D799FC300294721 /* AppConfigurationTranslationView.swift */; }; 4FA3741F2D79A64F00294721 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA3741E2D79A64900294721 /* AppConfiguration.swift */; }; @@ -614,12 +615,13 @@ 4F6D83342C0F05040098C298 /* PollCommentsViewModel_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollCommentsViewModel_Tests.swift; sourceTree = ""; }; 4F6D83502C1079A00098C298 /* AlertBannerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBannerViewModifier.swift; sourceTree = ""; }; 4F6D83532C1094220098C298 /* AlertBannerViewModifier_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBannerViewModifier_Tests.swift; sourceTree = ""; }; - 4F7613782DDCB2AD00F996E3 /* ChannelListDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListDateFormatter.swift; sourceTree = ""; }; + 4F7613782DDCB2AD00F996E3 /* MessageRelativeDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRelativeDateFormatter.swift; sourceTree = ""; }; 4F7720AD2C58C45000BAEC02 /* OnLoadViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnLoadViewModifier.swift; sourceTree = ""; }; 4F7DD99F2BFC7C6100599AA6 /* ChatClient+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatClient+Extensions.swift"; sourceTree = ""; }; 4F7DD9A12BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatClientExtensions_Tests.swift; sourceTree = ""; }; 4F889C552D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageExtensions_Tests.swift; sourceTree = ""; }; - 4F8D643F2DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListDateFormatter_Tests.swift; sourceTree = ""; }; + 4F8D643F2DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRelativeDateFormatter_Tests.swift; sourceTree = ""; }; + 4F9173FC2DDDFFE3003C30B5 /* ChannelListConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListConfig.swift; sourceTree = ""; }; 4FA374192D799CA400294721 /* AppConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationView.swift; sourceTree = ""; }; 4FA3741C2D799FC300294721 /* AppConfigurationTranslationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationTranslationView.swift; sourceTree = ""; }; 4FA3741E2D79A64900294721 /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; @@ -1928,7 +1930,6 @@ 4FCD7DBC2D633F6C000EEB0F /* AttributedString+Extensions.swift */, 8465FD392746A95600AF091E /* AutoLayoutHelpers.swift */, 8465FD452746A95600AF091E /* Cache.swift */, - 4F7613782DDCB2AD00F996E3 /* ChannelListDateFormatter.swift */, 8465FD412746A95600AF091E /* ChatChannelNamer.swift */, 4F7DD99F2BFC7C6100599AA6 /* ChatClient+Extensions.swift */, 8465FD442746A95600AF091E /* ChatMessage+Extensions.swift */, @@ -1940,6 +1941,7 @@ 8465FD3D2746A95600AF091E /* ImageCDN.swift */, 8465FD482746A95600AF091E /* ImageMerger.swift */, 8465FD422746A95600AF091E /* InputTextView.swift */, + 4F7613782DDCB2AD00F996E3 /* MessageRelativeDateFormatter.swift */, 8465FD432746A95600AF091E /* NSLayoutConstraint+Extensions.swift */, 8465FD492746A95600AF091E /* NukeImageProcessor.swift */, 4F7720AD2C58C45000BAEC02 /* OnLoadViewModifier.swift */, @@ -1956,22 +1958,23 @@ 8465FD4C2746A95600AF091E /* ChatChannelList */ = { isa = PBXGroup; children = ( + 8465FD4D2746A95600AF091E /* ChannelAvatarsMerger.swift */, + 8465FD572746A95700AF091E /* ChannelHeaderLoader.swift */, + 4F9173FC2DDDFFE3003C30B5 /* ChannelListConfig.swift */, + 8465FD4E2746A95600AF091E /* ChatChannelHelperViews.swift */, + 8465FD512746A95600AF091E /* ChatChannelList.swift */, + 8465FD542746A95700AF091E /* ChatChannelListHeader.swift */, + 8465FD592746A95700AF091E /* ChatChannelListItem.swift */, 8465FD552746A95700AF091E /* ChatChannelListScreen.swift */, 8465FD5C2746A95700AF091E /* ChatChannelListView.swift */, - 8465FD512746A95600AF091E /* ChatChannelList.swift */, 8465FD582746A95700AF091E /* ChatChannelListViewModel.swift */, - 8465FD542746A95700AF091E /* ChatChannelListHeader.swift */, - 8465FD5A2746A95700AF091E /* ChatChannelSwipeableListItem.swift */, 8465FD532746A95600AF091E /* ChatChannelNavigatableListItem.swift */, - 8465FD592746A95700AF091E /* ChatChannelListItem.swift */, - 8465FD4D2746A95600AF091E /* ChannelAvatarsMerger.swift */, - 8465FD4E2746A95600AF091E /* ChatChannelHelperViews.swift */, + 8465FD5A2746A95700AF091E /* ChatChannelSwipeableListItem.swift */, 8465FD502746A95600AF091E /* DefaultChannelActions.swift */, - 8465FD522746A95600AF091E /* NoChannelsView.swift */, - 8465FD572746A95700AF091E /* ChannelHeaderLoader.swift */, + 91B763A3283EB19800B458A9 /* MoreChannelActionsFullScreenWrappingView.swift */, 8465FD4F2746A95600AF091E /* MoreChannelActionsView.swift */, 8465FD5B2746A95700AF091E /* MoreChannelActionsViewModel.swift */, - 91B763A3283EB19800B458A9 /* MoreChannelActionsFullScreenWrappingView.swift */, + 8465FD522746A95600AF091E /* NoChannelsView.swift */, 8421BCEF27A44EAE000F977D /* SearchResultsView.swift */, ); path = ChatChannelList; @@ -2185,13 +2188,13 @@ isa = PBXGroup; children = ( 84D6E52B2B3078D200D0056C /* AudioRecordingNameFormatter_Tests.swift */, - 4F8D643F2DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift */, 84C94D61275A5BB7007FE2B9 /* ChatChannelNamer_Tests.swift */, 4F7DD9A12BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift */, 4F889C552D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift */, 91B79FD8284E7E9C005B6E4F /* ChatUserNamer_Tests.swift */, 84C94D53275A1380007FE2B9 /* DateUtils_Tests.swift */, 84C94D5D275A3AA9007FE2B9 /* ImageCDN_Tests.swift */, + 4F8D643F2DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift */, 849988AF2AE6BE4800CC95C9 /* PaddingsConfig_Tests.swift */, 84779C762AEBCA6E000A6A68 /* ReactionsIconProvider_Tests.swift */, 84E1D8272976CCAF00060491 /* SortReactions_Tests.swift */, @@ -2799,6 +2802,7 @@ 82D64C082AD7E5B700C5C79E /* Operation.swift in Sources */, 82D64BEB2AD7E5B700C5C79E /* ImagePipelineTask.swift in Sources */, AD2DDA612CB040EA0040B8D4 /* NoThreadsView.swift in Sources */, + 4F9173FD2DDDFFE8003C30B5 /* ChannelListConfig.swift in Sources */, 82D64BF92AD7E5B700C5C79E /* ImageProcessors+Resize.swift in Sources */, 8465FDC22746A95700AF091E /* ChatChannelNavigatableListItem.swift in Sources */, 8465FDAD2746A95700AF091E /* ImageCDN.swift in Sources */, @@ -2812,7 +2816,7 @@ 8465FDA52746A95700AF091E /* Modifiers.swift in Sources */, 8465FDBB2746A95700AF091E /* LoadingView.swift in Sources */, 84D6E4F62B2CA4E300D0056C /* RecordingTipView.swift in Sources */, - 4F7613792DDCB2C900F996E3 /* ChannelListDateFormatter.swift in Sources */, + 4F7613792DDCB2C900F996E3 /* MessageRelativeDateFormatter.swift in Sources */, 846608E3278C303800D3D7B3 /* TypingIndicatorView.swift in Sources */, 84A1CACF2816BCF00046595A /* AddUsersView.swift in Sources */, 82D64BF02AD7E5B700C5C79E /* DataLoading.swift in Sources */, @@ -3072,7 +3076,7 @@ 84B2B5CA281947E100479CEE /* ViewFrameUtils.swift in Sources */, 8423C342277CBA280092DCF1 /* TypingSuggester_Tests.swift in Sources */, 84507C9A281ACCD70081DDC2 /* AddUsersView_Tests.swift in Sources */, - 4F8D64402DDDCF9300026C09 /* ChannelListDateFormatter_Tests.swift in Sources */, + 4F8D64402DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift in Sources */, 84C94D0627578BF2007FE2B9 /* UnwrapAsync.swift in Sources */, 84D6B55A27DF6EC7009C6D07 /* LoadingView_Tests.swift in Sources */, 84C94D0427578BF2007FE2B9 /* TestError.swift in Sources */, diff --git a/StreamChatSwiftUITests/Tests/Utils/ChannelListDateFormatter_Tests.swift b/StreamChatSwiftUITests/Tests/Utils/MessageRelativeDateFormatter_Tests.swift similarity index 70% rename from StreamChatSwiftUITests/Tests/Utils/ChannelListDateFormatter_Tests.swift rename to StreamChatSwiftUITests/Tests/Utils/MessageRelativeDateFormatter_Tests.swift index c646287df..af8412a4e 100644 --- a/StreamChatSwiftUITests/Tests/Utils/ChannelListDateFormatter_Tests.swift +++ b/StreamChatSwiftUITests/Tests/Utils/MessageRelativeDateFormatter_Tests.swift @@ -7,24 +7,26 @@ import Foundation @testable import StreamChatSwiftUI import XCTest -final class ChannelListDateFormatter_Tests: StreamChatTestCase { - private var formatter: ChannelListDateFormatter! +final class MessageRelativeDateFormatter_Tests: StreamChatTestCase { + private var formatter: MessageRelativeDateFormatter! - override func setUpWithError() throws { - try super.setUpWithError() - formatter = ChannelListDateFormatter() + override func setUp() { + super.setUp() + formatter = MessageRelativeDateFormatter() formatter.locale = Locale(identifier: "en_UK") + formatter.todayFormatter.locale = Locale(identifier: "en_UK") + formatter.yesterdayFormatter.locale = Locale(identifier: "en_UK") } - override func tearDownWithError() throws { - try super.tearDownWithError() + override func tearDown() { + super.tearDown() formatter = nil } func test_showingTimeOnly() throws { let date = try XCTUnwrap(Calendar.current.date(bySettingHour: 1, minute: 2, second: 3, of: Date())) let result = formatter.string(from: date) - let expected = formatter.time.string(from: date) + let expected = formatter.todayFormatter.string(from: date) XCTAssertEqual(expected, result) XCTAssertEqual("01:02", result) } @@ -32,7 +34,7 @@ final class ChannelListDateFormatter_Tests: StreamChatTestCase { func test_showingYesterday() throws { let date = try XCTUnwrap(Calendar.current.date(byAdding: .day, value: -1, to: Date())) let result = formatter.string(from: date) - let expected = formatter.shortRelativeDate.string(from: date) + let expected = formatter.yesterdayFormatter.string(from: date) XCTAssertEqual(expected, result) XCTAssertEqual("Yesterday", result) } @@ -40,7 +42,7 @@ final class ChannelListDateFormatter_Tests: StreamChatTestCase { func test_showingWeekday() throws { let date = try XCTUnwrap(Calendar.current.date(byAdding: .day, value: -6, to: Date())) let result = formatter.string(from: date) - let expected = formatter.weekday.string(from: date) + let expected = formatter.weekdayFormatter.string(from: date) XCTAssertEqual(expected, result) }