Skip to content

Commit 2b23197

Browse files
authored
Show dynamically time, relative date, weekday, or short date in channel lists (#833)
1 parent 68ecf4f commit 2b23197

File tree

9 files changed

+188
-11
lines changed

9 files changed

+188
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
### ✅ Added
77
- Add extra data to user display info [#819](https://github.com/GetStream/stream-chat-swiftui/pull/819)
88
- Make message spacing in message list configurable [#830](https://github.com/GetStream/stream-chat-swiftui/pull/830)
9+
- 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)
10+
- Set `ChannelListConfig.messageRelativeDateFormatEnabled` to true for enabling it
911
- Add `MessageViewModel` to `MessageContainerView` to make it easier to customise presentation logic [#815](https://github.com/GetStream/stream-chat-swiftui/pull/815)
1012
- Add `MessageListConfig.messaeDisplayOptions.showOriginalTranslatedButton` to enable showing original text in translated message [#815](https://github.com/GetStream/stream-chat-swiftui/pull/815)
1113
- Add `Utils.originalTranslationsStore` to keep track of messages that should show the original text [#815](https://github.com/GetStream/stream-chat-swiftui/pull/815)

DemoAppSwiftUI/AppDelegate.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class AppDelegate: NSObject, UIApplicationDelegate {
6363
#endif
6464

6565
let utils = Utils(
66+
channelListConfig: ChannelListConfig(
67+
messageRelativeDateFormatEnabled: true
68+
),
6669
messageListConfig: MessageListConfig(
6770
messageDisplayOptions: .init(showOriginalTranslatedButton: true),
6871
dateIndicatorPlacement: .messageList,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
7+
/// A configuration for channel lists.
8+
public struct ChannelListConfig {
9+
public init(messageRelativeDateFormatEnabled: Bool = false) {
10+
self.messageRelativeDateFormatEnabled = messageRelativeDateFormatEnabled
11+
}
12+
13+
/// If true, the timestamp format depends on the time passed.
14+
///
15+
/// Different date formats are used for today, yesterday, last 7 days, and older dates.
16+
public var messageRelativeDateFormatEnabled: Bool
17+
}

Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListItem.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,11 @@ extension ChatChannel {
337337

338338
public var timestampText: String {
339339
if let lastMessageAt = lastMessageAt {
340-
return InjectedValues[\.utils].dateFormatter.string(from: lastMessageAt)
340+
let utils = InjectedValues[\.utils]
341+
let formatter = utils.channelListConfig.messageRelativeDateFormatEnabled ?
342+
utils.messageRelativeDateFormatter :
343+
utils.dateFormatter
344+
return formatter.string(from: lastMessageAt)
341345
} else {
342346
return ""
343347
}

Sources/StreamChatSwiftUI/ChatChannelList/SearchResultsView.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ struct SearchResultItem<Factory: ViewFactory, ChannelDestination: View>: View {
158158

159159
private var timestampText: String {
160160
if let lastMessageAt = searchResult.channel.lastMessageAt {
161-
return utils.dateFormatter.string(from: lastMessageAt)
161+
let formatter = utils.channelListConfig.messageRelativeDateFormatEnabled ?
162+
utils.messageRelativeDateFormatter :
163+
utils.dateFormatter
164+
return formatter.string(from: lastMessageAt)
162165
} else {
163166
return ""
164167
}

Sources/StreamChatSwiftUI/Utils.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public class Utils {
1313
var markdownFormatter = MarkdownFormatter()
1414

1515
public var dateFormatter: DateFormatter
16+
17+
/// Date formatter where the format depends on the time passed.
18+
///
19+
/// - SeeAlso: ``ChannelListConfig/messageRelativeDateFormatEnabled``.
20+
public var messageRelativeDateFormatter: DateFormatter
1621
public var videoPreviewLoader: VideoPreviewLoader
1722
public var imageLoader: ImageLoading
1823
public var imageCDN: ImageCDN
@@ -25,6 +30,7 @@ public class Utils {
2530
public var messageTypeResolver: MessageTypeResolving
2631
public var messageActionsResolver: MessageActionsResolving
2732
public var commandsConfig: CommandsConfig
33+
public var channelListConfig: ChannelListConfig
2834
public var messageListConfig: MessageListConfig
2935
public var composerConfig: ComposerConfig
3036
public var pollsConfig: PollsConfig
@@ -71,6 +77,7 @@ public class Utils {
7177

7278
public init(
7379
dateFormatter: DateFormatter = .makeDefault(),
80+
messageRelativeDateFormatter: DateFormatter = MessageRelativeDateFormatter(),
7481
videoPreviewLoader: VideoPreviewLoader = DefaultVideoPreviewLoader(),
7582
imageLoader: ImageLoading = NukeImageLoader(),
7683
imageCDN: ImageCDN = StreamImageCDN(),
@@ -81,6 +88,7 @@ public class Utils {
8188
messageTypeResolver: MessageTypeResolving = MessageTypeResolver(),
8289
messageActionResolver: MessageActionsResolving = MessageActionsResolver(),
8390
commandsConfig: CommandsConfig = DefaultCommandsConfig(),
91+
channelListConfig: ChannelListConfig = ChannelListConfig(),
8492
messageListConfig: MessageListConfig = MessageListConfig(),
8593
composerConfig: ComposerConfig = ComposerConfig(),
8694
pollsConfig: PollsConfig = PollsConfig(),
@@ -95,6 +103,7 @@ public class Utils {
95103
shouldSyncChannelControllerOnAppear: @escaping (ChatChannelController) -> Bool = { _ in true }
96104
) {
97105
self.dateFormatter = dateFormatter
106+
self.messageRelativeDateFormatter = messageRelativeDateFormatter
98107
self.videoPreviewLoader = videoPreviewLoader
99108
self.imageLoader = imageLoader
100109
self.imageCDN = imageCDN
@@ -107,6 +116,7 @@ public class Utils {
107116
self.messageTypeResolver = messageTypeResolver
108117
messageActionsResolver = messageActionResolver
109118
self.commandsConfig = commandsConfig
119+
self.channelListConfig = channelListConfig
110120
self.messageListConfig = messageListConfig
111121
self.composerConfig = composerConfig
112122
self.snapshotCreator = snapshotCreator
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
7+
/// A formatter that converts message timestamps to a format which depends on the time passed.
8+
public final class MessageRelativeDateFormatter: DateFormatter, @unchecked Sendable {
9+
override public init() {
10+
super.init()
11+
locale = .autoupdatingCurrent
12+
dateStyle = .short
13+
timeStyle = .none
14+
}
15+
16+
@available(*, unavailable)
17+
required init?(coder: NSCoder) {
18+
fatalError("init(coder:) has not been implemented")
19+
}
20+
21+
override public func string(from date: Date) -> String {
22+
if calendar.isDateInToday(date) {
23+
return todayFormatter.string(from: date)
24+
}
25+
if calendar.isDateInYesterday(date) {
26+
return yesterdayFormatter.string(from: date)
27+
}
28+
if calendar.isDateInLastWeek(date) {
29+
return weekdayFormatter.string(from: date)
30+
}
31+
32+
return super.string(from: date)
33+
}
34+
35+
var todayFormatter: DateFormatter {
36+
InjectedValues[\.utils].dateFormatter
37+
}
38+
39+
let yesterdayFormatter: DateFormatter = {
40+
let formatter = DateFormatter()
41+
formatter.locale = .autoupdatingCurrent
42+
formatter.dateStyle = .short
43+
formatter.timeStyle = .none
44+
formatter.doesRelativeDateFormatting = true
45+
return formatter
46+
}()
47+
48+
let weekdayFormatter: DateFormatter = {
49+
let formatter = DateFormatter()
50+
formatter.locale = .autoupdatingCurrent
51+
formatter.setLocalizedDateFormatFromTemplate("EEEE")
52+
return formatter
53+
}()
54+
}
55+
56+
extension Calendar {
57+
func isDateInLastWeek(_ date: Date) -> Bool {
58+
guard let dateBefore7days = self.date(byAdding: .day, value: -7, to: Date()) else {
59+
return false
60+
}
61+
return date > dateBefore7days
62+
}
63+
}

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
4F6D83352C0F05040098C298 /* PollCommentsViewModel_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83342C0F05040098C298 /* PollCommentsViewModel_Tests.swift */; };
1717
4F6D83512C1079A00098C298 /* AlertBannerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83502C1079A00098C298 /* AlertBannerViewModifier.swift */; };
1818
4F6D83542C1094220098C298 /* AlertBannerViewModifier_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D83532C1094220098C298 /* AlertBannerViewModifier_Tests.swift */; };
19+
4F7613792DDCB2C900F996E3 /* MessageRelativeDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7613782DDCB2AD00F996E3 /* MessageRelativeDateFormatter.swift */; };
1920
4F7720AE2C58C45200BAEC02 /* OnLoadViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7720AD2C58C45000BAEC02 /* OnLoadViewModifier.swift */; };
2021
4F7DD9A02BFC7C6100599AA6 /* ChatClient+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7DD99F2BFC7C6100599AA6 /* ChatClient+Extensions.swift */; };
2122
4F7DD9A22BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7DD9A12BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift */; };
2223
4F889C562D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F889C552D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift */; };
24+
4F8D64402DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8D643F2DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift */; };
25+
4F9173FD2DDDFFE8003C30B5 /* ChannelListConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F9173FC2DDDFFE3003C30B5 /* ChannelListConfig.swift */; };
2326
4FA3741A2D799CA400294721 /* AppConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA374192D799CA400294721 /* AppConfigurationView.swift */; };
2427
4FA3741D2D799FC300294721 /* AppConfigurationTranslationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA3741C2D799FC300294721 /* AppConfigurationTranslationView.swift */; };
2528
4FA3741F2D79A64F00294721 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA3741E2D79A64900294721 /* AppConfiguration.swift */; };
@@ -614,10 +617,13 @@
614617
4F6D83342C0F05040098C298 /* PollCommentsViewModel_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollCommentsViewModel_Tests.swift; sourceTree = "<group>"; };
615618
4F6D83502C1079A00098C298 /* AlertBannerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBannerViewModifier.swift; sourceTree = "<group>"; };
616619
4F6D83532C1094220098C298 /* AlertBannerViewModifier_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBannerViewModifier_Tests.swift; sourceTree = "<group>"; };
620+
4F7613782DDCB2AD00F996E3 /* MessageRelativeDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRelativeDateFormatter.swift; sourceTree = "<group>"; };
617621
4F7720AD2C58C45000BAEC02 /* OnLoadViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnLoadViewModifier.swift; sourceTree = "<group>"; };
618622
4F7DD99F2BFC7C6100599AA6 /* ChatClient+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatClient+Extensions.swift"; sourceTree = "<group>"; };
619623
4F7DD9A12BFCB2EF00599AA6 /* ChatClientExtensions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatClientExtensions_Tests.swift; sourceTree = "<group>"; };
620624
4F889C552D7F000700A7BDAF /* ChatMessageExtensions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageExtensions_Tests.swift; sourceTree = "<group>"; };
625+
4F8D643F2DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRelativeDateFormatter_Tests.swift; sourceTree = "<group>"; };
626+
4F9173FC2DDDFFE3003C30B5 /* ChannelListConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListConfig.swift; sourceTree = "<group>"; };
621627
4FA374192D799CA400294721 /* AppConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationView.swift; sourceTree = "<group>"; };
622628
4FA3741C2D799FC300294721 /* AppConfigurationTranslationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationTranslationView.swift; sourceTree = "<group>"; };
623629
4FA3741E2D79A64900294721 /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = "<group>"; };
@@ -1941,6 +1947,7 @@
19411947
8465FD3D2746A95600AF091E /* ImageCDN.swift */,
19421948
8465FD482746A95600AF091E /* ImageMerger.swift */,
19431949
8465FD422746A95600AF091E /* InputTextView.swift */,
1950+
4F7613782DDCB2AD00F996E3 /* MessageRelativeDateFormatter.swift */,
19441951
8465FD432746A95600AF091E /* NSLayoutConstraint+Extensions.swift */,
19451952
8465FD492746A95600AF091E /* NukeImageProcessor.swift */,
19461953
4F7720AD2C58C45000BAEC02 /* OnLoadViewModifier.swift */,
@@ -1957,22 +1964,23 @@
19571964
8465FD4C2746A95600AF091E /* ChatChannelList */ = {
19581965
isa = PBXGroup;
19591966
children = (
1967+
8465FD4D2746A95600AF091E /* ChannelAvatarsMerger.swift */,
1968+
8465FD572746A95700AF091E /* ChannelHeaderLoader.swift */,
1969+
4F9173FC2DDDFFE3003C30B5 /* ChannelListConfig.swift */,
1970+
8465FD4E2746A95600AF091E /* ChatChannelHelperViews.swift */,
1971+
8465FD512746A95600AF091E /* ChatChannelList.swift */,
1972+
8465FD542746A95700AF091E /* ChatChannelListHeader.swift */,
1973+
8465FD592746A95700AF091E /* ChatChannelListItem.swift */,
19601974
8465FD552746A95700AF091E /* ChatChannelListScreen.swift */,
19611975
8465FD5C2746A95700AF091E /* ChatChannelListView.swift */,
1962-
8465FD512746A95600AF091E /* ChatChannelList.swift */,
19631976
8465FD582746A95700AF091E /* ChatChannelListViewModel.swift */,
1964-
8465FD542746A95700AF091E /* ChatChannelListHeader.swift */,
1965-
8465FD5A2746A95700AF091E /* ChatChannelSwipeableListItem.swift */,
19661977
8465FD532746A95600AF091E /* ChatChannelNavigatableListItem.swift */,
1967-
8465FD592746A95700AF091E /* ChatChannelListItem.swift */,
1968-
8465FD4D2746A95600AF091E /* ChannelAvatarsMerger.swift */,
1969-
8465FD4E2746A95600AF091E /* ChatChannelHelperViews.swift */,
1978+
8465FD5A2746A95700AF091E /* ChatChannelSwipeableListItem.swift */,
19701979
8465FD502746A95600AF091E /* DefaultChannelActions.swift */,
1971-
8465FD522746A95600AF091E /* NoChannelsView.swift */,
1972-
8465FD572746A95700AF091E /* ChannelHeaderLoader.swift */,
1980+
91B763A3283EB19800B458A9 /* MoreChannelActionsFullScreenWrappingView.swift */,
19731981
8465FD4F2746A95600AF091E /* MoreChannelActionsView.swift */,
19741982
8465FD5B2746A95700AF091E /* MoreChannelActionsViewModel.swift */,
1975-
91B763A3283EB19800B458A9 /* MoreChannelActionsFullScreenWrappingView.swift */,
1983+
8465FD522746A95600AF091E /* NoChannelsView.swift */,
19761984
8421BCEF27A44EAE000F977D /* SearchResultsView.swift */,
19771985
);
19781986
path = ChatChannelList;
@@ -2192,6 +2200,7 @@
21922200
91B79FD8284E7E9C005B6E4F /* ChatUserNamer_Tests.swift */,
21932201
84C94D53275A1380007FE2B9 /* DateUtils_Tests.swift */,
21942202
84C94D5D275A3AA9007FE2B9 /* ImageCDN_Tests.swift */,
2203+
4F8D643F2DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift */,
21952204
849988AF2AE6BE4800CC95C9 /* PaddingsConfig_Tests.swift */,
21962205
84779C762AEBCA6E000A6A68 /* ReactionsIconProvider_Tests.swift */,
21972206
84E1D8272976CCAF00060491 /* SortReactions_Tests.swift */,
@@ -2801,6 +2810,7 @@
28012810
82D64C082AD7E5B700C5C79E /* Operation.swift in Sources */,
28022811
82D64BEB2AD7E5B700C5C79E /* ImagePipelineTask.swift in Sources */,
28032812
AD2DDA612CB040EA0040B8D4 /* NoThreadsView.swift in Sources */,
2813+
4F9173FD2DDDFFE8003C30B5 /* ChannelListConfig.swift in Sources */,
28042814
82D64BF92AD7E5B700C5C79E /* ImageProcessors+Resize.swift in Sources */,
28052815
8465FDC22746A95700AF091E /* ChatChannelNavigatableListItem.swift in Sources */,
28062816
8465FDAD2746A95700AF091E /* ImageCDN.swift in Sources */,
@@ -2814,6 +2824,7 @@
28142824
8465FDA52746A95700AF091E /* Modifiers.swift in Sources */,
28152825
8465FDBB2746A95700AF091E /* LoadingView.swift in Sources */,
28162826
84D6E4F62B2CA4E300D0056C /* RecordingTipView.swift in Sources */,
2827+
4F7613792DDCB2C900F996E3 /* MessageRelativeDateFormatter.swift in Sources */,
28172828
846608E3278C303800D3D7B3 /* TypingIndicatorView.swift in Sources */,
28182829
84A1CACF2816BCF00046595A /* AddUsersView.swift in Sources */,
28192830
82D64BF02AD7E5B700C5C79E /* DataLoading.swift in Sources */,
@@ -3073,6 +3084,7 @@
30733084
84B2B5CA281947E100479CEE /* ViewFrameUtils.swift in Sources */,
30743085
8423C342277CBA280092DCF1 /* TypingSuggester_Tests.swift in Sources */,
30753086
84507C9A281ACCD70081DDC2 /* AddUsersView_Tests.swift in Sources */,
3087+
4F8D64402DDDCF9300026C09 /* MessageRelativeDateFormatter_Tests.swift in Sources */,
30763088
84C94D0627578BF2007FE2B9 /* UnwrapAsync.swift in Sources */,
30773089
84D6B55A27DF6EC7009C6D07 /* LoadingView_Tests.swift in Sources */,
30783090
84C94D0427578BF2007FE2B9 /* TestError.swift in Sources */,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
@testable import StreamChat
7+
@testable import StreamChatSwiftUI
8+
import XCTest
9+
10+
final class MessageRelativeDateFormatter_Tests: StreamChatTestCase {
11+
private var formatter: MessageRelativeDateFormatter!
12+
13+
override func setUp() {
14+
super.setUp()
15+
formatter = MessageRelativeDateFormatter()
16+
formatter.locale = Locale(identifier: "en_UK")
17+
formatter.todayFormatter.locale = Locale(identifier: "en_UK")
18+
formatter.yesterdayFormatter.locale = Locale(identifier: "en_UK")
19+
}
20+
21+
override func tearDown() {
22+
super.tearDown()
23+
formatter = nil
24+
}
25+
26+
func test_showingTimeOnly() throws {
27+
let date = try XCTUnwrap(Calendar.current.date(bySettingHour: 1, minute: 2, second: 3, of: Date()))
28+
let result = formatter.string(from: date)
29+
let expected = formatter.todayFormatter.string(from: date)
30+
XCTAssertEqual(expected, result)
31+
XCTAssertEqual("01:02", result)
32+
}
33+
34+
func test_showingYesterday() throws {
35+
let date = try XCTUnwrap(Calendar.current.date(byAdding: .day, value: -1, to: Date()))
36+
let result = formatter.string(from: date)
37+
let expected = formatter.yesterdayFormatter.string(from: date)
38+
XCTAssertEqual(expected, result)
39+
XCTAssertEqual("Yesterday", result)
40+
}
41+
42+
func test_showingWeekday() throws {
43+
let date = try XCTUnwrap(Calendar.current.date(byAdding: .day, value: -6, to: Date()))
44+
let result = formatter.string(from: date)
45+
let expected = formatter.weekdayFormatter.string(from: date)
46+
XCTAssertEqual(expected, result)
47+
}
48+
49+
func test_showingShortDate() throws {
50+
let components = DateComponents(
51+
timeZone: TimeZone(secondsFromGMT: 0),
52+
year: 2025,
53+
month: 1,
54+
day: 15,
55+
hour: 3,
56+
minute: 4,
57+
second: 5
58+
)
59+
let date = try XCTUnwrap(Calendar.current.date(from: components))
60+
let result = formatter.string(from: date)
61+
XCTAssertEqual("15/01/2025", result)
62+
}
63+
}

0 commit comments

Comments
 (0)