Sounds: - Add chat sounds (global, map, DM) converted from game client ogg to caf - Audio category .ambient (mixes with other audio, respects silent switch) - Per-buffer sound toggles and background buffer sound option in Settings Messages: - Card-style message rows with .regularMaterial background, bumped fonts (content .title3, sender .headline) - Context menu and VoiceOver actions: DM and Show Profile on other players' messages - Generate local UUIDs for message identity (server ID field is not unique, caused ForEach to collapse messages) Own-message detection: - Capture player entity ID from LoadInstance packet (Key 1) on game join - Hide DM/Profile actions on own messages via senderId comparison Crash fix: - Guard all UInt64 timestamp subtractions against underflow (retransmitPending, ping, disconnect timeout)
71 lines
2.4 KiB
Swift
71 lines
2.4 KiB
Swift
import SwiftUI
|
|
|
|
struct MessageRow: View {
|
|
let message: ChatMessage
|
|
var onDM: ((ChatMessage) -> Void)?
|
|
var onProfile: ((ChatMessage) -> Void)?
|
|
@Environment(ConnectionManager.self) private var manager
|
|
|
|
private var isOwnMessage: Bool {
|
|
message.senderIsRecipient || message.senderId == manager.localSenderId
|
|
}
|
|
|
|
var body: some View {
|
|
if message.isSystem {
|
|
Text(message.content)
|
|
.font(.body)
|
|
.foregroundStyle(.secondary)
|
|
.italic()
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
.padding(.vertical, 6)
|
|
} else {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
HStack {
|
|
Text(message.senderName)
|
|
.font(.headline)
|
|
.foregroundStyle(Color.accentColor)
|
|
|
|
if message.senderIsRecipient {
|
|
Text("(you)")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Text(message.timestamp, style: .time)
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Text(message.content)
|
|
.font(.title3)
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 10)
|
|
.background(.regularMaterial)
|
|
.clipShape(.rect(cornerRadius: 12))
|
|
.contextMenu {
|
|
if !isOwnMessage, message.senderId != nil {
|
|
if let onDM {
|
|
Button("DM \(message.senderName)", systemImage: "envelope") { onDM(message) }
|
|
}
|
|
if let onProfile {
|
|
Button("Show \(message.senderName)'s Profile", systemImage: "person.circle") { onProfile(message) }
|
|
}
|
|
}
|
|
}
|
|
.accessibilityElement(children: .combine)
|
|
.accessibilityActions {
|
|
if !isOwnMessage, message.senderId != nil {
|
|
if let onDM {
|
|
Button("DM \(message.senderName)") { onDM(message) }
|
|
}
|
|
if let onProfile {
|
|
Button("Show \(message.senderName)'s Profile") { onProfile(message) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|