Add iOS pinned messages bar

This commit is contained in:
Mikhail Kilin
2026-05-21 15:39:16 +03:00
parent 419f409d98
commit da41e1ed91

View File

@@ -341,63 +341,75 @@ public struct ChatDetailView: View {
public var body: some View { public var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
List { ScrollViewReader { scrollProxy in
if !viewModel.searchText.isEmpty { VStack(spacing: 0) {
Section("Search") { if !viewModel.pinnedMessages.isEmpty {
if viewModel.searchResults.isEmpty { PinnedMessagesBar(messages: viewModel.pinnedMessages) { message in
Text("No results") withAnimation {
.foregroundStyle(.secondary) scrollProxy.scrollTo(message.id, anchor: .center)
} else {
ForEach(viewModel.searchResults) { message in
MessageRow(message: message)
.listRowSeparator(.hidden)
} }
} }
} }
} List {
Section { if !viewModel.searchText.isEmpty {
ForEach(viewModel.messages) { message in Section("Search") {
MessageRow(message: message) if viewModel.searchResults.isEmpty {
.contextMenu { Text("No results")
Button { .foregroundStyle(.secondary)
viewModel.beginReply(to: message) } else {
} label: { ForEach(viewModel.searchResults) { message in
Label("Reply", systemImage: "arrowshape.turn.up.left") MessageRow(message: message)
} .listRowSeparator(.hidden)
Button {
editingMessage = message
editedText = message.text
} label: {
Label("Edit", systemImage: "pencil")
}
Button {
forwardCandidate = message
forwardChatIdText = ""
} label: {
Label("Forward", systemImage: "arrowshape.turn.up.forward")
}
Button {
Task { await viewModel.react(message: message, reaction: "👍") }
} label: {
Label("React", systemImage: "face.smiling")
}
Button {
Task {
await viewModel.copyPayload(for: message)
if let payload = viewModel.copiedPayload {
await clipboard.write(text: payload)
}
} }
} label: {
Label("Copy", systemImage: "doc.on.doc")
}
Button(role: .destructive) {
deleteCandidate = message
} label: {
Label("Delete", systemImage: "trash")
} }
} }
.listRowSeparator(.hidden) }
Section {
ForEach(viewModel.messages) { message in
MessageRow(message: message)
.id(message.id)
.contextMenu {
Button {
viewModel.beginReply(to: message)
} label: {
Label("Reply", systemImage: "arrowshape.turn.up.left")
}
Button {
editingMessage = message
editedText = message.text
} label: {
Label("Edit", systemImage: "pencil")
}
Button {
forwardCandidate = message
forwardChatIdText = ""
} label: {
Label("Forward", systemImage: "arrowshape.turn.up.forward")
}
Button {
Task { await viewModel.react(message: message, reaction: "👍") }
} label: {
Label("React", systemImage: "face.smiling")
}
Button {
Task {
await viewModel.copyPayload(for: message)
if let payload = viewModel.copiedPayload {
await clipboard.write(text: payload)
}
}
} label: {
Label("Copy", systemImage: "doc.on.doc")
}
Button(role: .destructive) {
deleteCandidate = message
} label: {
Label("Delete", systemImage: "trash")
}
}
.listRowSeparator(.hidden)
}
}
} }
} }
} }
@@ -497,6 +509,49 @@ public struct ChatDetailView: View {
} }
} }
public struct PinnedMessagesBar: View {
public var messages: [Message]
public var select: (Message) -> Void
public init(messages: [Message], select: @escaping (Message) -> Void) {
self.messages = messages
self.select = select
}
public var body: some View {
VStack(spacing: 0) {
ForEach(messages.prefix(3)) { message in
Button {
select(message)
} label: {
HStack(spacing: 8) {
Image(systemName: "pin.fill")
.font(.caption)
.foregroundStyle(.blue)
VStack(alignment: .leading, spacing: 2) {
Text(message.senderName)
.font(.caption2)
.foregroundStyle(.secondary)
Text(message.text)
.font(.subheadline)
.lineLimit(1)
}
Spacer()
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
}
.background(Color.blue.opacity(0.08))
.overlay(alignment: .bottom) {
Divider()
}
}
}
public struct MessageRow: View { public struct MessageRow: View {
public var message: Message public var message: Message