Add iOS message date grouping

This commit is contained in:
Mikhail Kilin
2026-05-21 15:45:48 +03:00
parent da41e1ed91
commit 508db79c34
5 changed files with 117 additions and 7 deletions

View File

@@ -365,8 +365,12 @@ public struct ChatDetailView: View {
}
}
Section {
ForEach(viewModel.messages) { message in
MessageRow(message: message)
ForEach(Array(viewModel.messages.enumerated()), id: \.element.id) { index, message in
if shouldShowDateSeparator(at: index) {
DateSeparatorView(timestamp: message.date)
.listRowSeparator(.hidden)
}
MessageRow(message: message, showsSender: shouldShowSender(at: index))
.id(message.id)
.contextMenu {
Button {
@@ -507,6 +511,76 @@ public struct ChatDetailView: View {
}
)
}
private func shouldShowDateSeparator(at index: Int) -> Bool {
guard viewModel.messages.indices.contains(index), viewModel.messages[index].date > 0 else {
return false
}
guard index > 0, viewModel.messages.indices.contains(index - 1) else {
return true
}
let current = Date(timeIntervalSince1970: TimeInterval(viewModel.messages[index].date))
let previous = Date(timeIntervalSince1970: TimeInterval(viewModel.messages[index - 1].date))
return !Calendar.current.isDate(current, inSameDayAs: previous)
}
private func shouldShowSender(at index: Int) -> Bool {
guard viewModel.messages.indices.contains(index) else {
return true
}
let message = viewModel.messages[index]
guard !message.isOutgoing else {
return false
}
guard index > 0, viewModel.messages.indices.contains(index - 1) else {
return true
}
let previous = viewModel.messages[index - 1]
return previous.isOutgoing
|| previous.senderName != message.senderName
|| shouldShowDateSeparator(at: index)
}
}
public struct DateSeparatorView: View {
public var timestamp: Int32
public init(timestamp: Int32) {
self.timestamp = timestamp
}
public var body: some View {
HStack {
Spacer()
Text(label)
.font(.caption)
.foregroundStyle(.secondary)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(Color.gray.opacity(0.12), in: Capsule())
Spacer()
}
.padding(.vertical, 6)
}
private var label: String {
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
let calendar = Calendar.current
if calendar.isDateInToday(date) {
return "Today"
}
if calendar.isDateInYesterday(date) {
return "Yesterday"
}
return Self.formatter.string(from: date)
}
private static let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
}
public struct PinnedMessagesBar: View {
@@ -554,9 +628,11 @@ public struct PinnedMessagesBar: View {
public struct MessageRow: View {
public var message: Message
public var showsSender: Bool
public init(message: Message) {
public init(message: Message, showsSender: Bool = true) {
self.message = message
self.showsSender = showsSender
}
public var body: some View {
@@ -565,7 +641,7 @@ public struct MessageRow: View {
Spacer(minLength: 48)
}
VStack(alignment: .leading, spacing: 5) {
if !message.isOutgoing {
if showsSender && !message.isOutgoing {
Text(message.senderName)
.font(.caption)
.foregroundStyle(.secondary)
@@ -587,8 +663,11 @@ public struct MessageRow: View {
Text(message.reactions.map(\.emoji).joined(separator: " "))
.font(.caption)
}
if message.editDate != nil || message.isOutgoing {
if message.editDate != nil || message.date > 0 || message.isOutgoing {
HStack(spacing: 6) {
if message.date > 0 {
Text(Self.timeFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(message.date))))
}
if message.editDate != nil {
Text("edited")
}
@@ -618,6 +697,13 @@ public struct MessageRow: View {
)
) ?? AttributedString(message.text)
}
private static let timeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
return formatter
}()
}
public struct ComposeBar: View {