Add iOS pinned messages bar
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user