KDChat/KDChat/Views/ChatBufferView.swift
Blake Oliver 2d3fefa013
Initial commit of KDChat
This is an iOS chat client for Kirandur, a multiplayer RPG. Reimplements the LiteNetLib UDP protocol in Swift for reliable ordered messaging. Renders server-driven UI via a RemoteForms widget system for login, character creation and more inside the client.

Uses msgpack-swift for serialization, Keychain for credentials, and targets iOS 18.6+/Xcode 26+.

No message persistence yet. No auto-reconnect. Game-specific packets (movement, combat, etc.) are silently ignored.
No profile viewing yet.

Assisted by Claude Code (Opus 4.6)
2026-04-05 07:11:07 -06:00

84 lines
3.1 KiB
Swift

import SwiftUI
struct ChatBufferView: View {
let bufferName: String
let canSend: Bool
var onDM: ((ChatMessage) -> Void)?
@Environment(ConnectionManager.self) private var manager
@State private var messageText = ""
private var messages: [ChatMessage] {
manager.messages[bufferName] ?? []
}
var body: some View {
VStack(spacing: 0) {
ScrollViewReader { proxy in
ScrollView {
LazyVStack(alignment: .leading, spacing: 8) {
ForEach(messages) { message in
MessageRow(
message: message,
onTapSender: message.isInteractable ? { startDM(to: message) } : nil,
onDM: onDM,
onProfile: nil // TODO: profile viewing
)
.id(message.id)
}
}
.padding()
}
.onChange(of: messages.count) {
if let lastId = messages.last?.id {
withAnimation(.easeOut(duration: 0.2)) {
proxy.scrollTo(lastId, anchor: .bottom)
}
}
}
}
if canSend {
HStack(spacing: 8) {
TextField("Message...", text: $messageText, axis: .vertical)
.textFieldStyle(.roundedBorder)
.lineLimit(1...4)
.onSubmit { sendMessage() }
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button("/command", systemImage: "slash.circle") {
if !messageText.hasPrefix("/") {
messageText = "/" + messageText
}
}
.labelStyle(.titleAndIcon)
Spacer()
}
}
Button("Send", systemImage: "arrow.up.circle.fill", action: sendMessage)
.labelStyle(.iconOnly)
.font(.title)
.buttonStyle(.borderedProminent)
.buttonBorderShape(.circle)
.disabled(messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
.padding(.horizontal)
.padding(.vertical, 8)
.background(.bar)
}
}
}
private func sendMessage() {
let text = messageText.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty else { return }
manager.sendChatMessage(buffer: bufferName, content: text)
messageText = ""
}
private func startDM(to message: ChatMessage) {
guard message.senderId != nil else { return }
// Full DM flow would use sendDirectMessage with a selected recipient
}
}