Compare commits
2 Commits
0ca801d963
...
ab90d244b5
| Author | SHA1 | Date | |
|---|---|---|---|
|
ab90d244b5
|
|||
|
dc6af6d9e5
|
@@ -679,14 +679,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@@ -744,14 +740,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
@@ -780,14 +772,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks.sh\"\n";
|
||||||
|
|||||||
@@ -260,6 +260,9 @@ struct ChatRoomView: View {
|
|||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var error: Error?
|
@State private var error: Error?
|
||||||
@State private var wsState: WebSocketState = .disconnected // New state for WebSocket status
|
@State private var wsState: WebSocketState = .disconnected // New state for WebSocket status
|
||||||
|
@State private var hasLoadedMessages = false // Track if messages have been loaded
|
||||||
|
@State private var messageText = "" // Text input for sending messages
|
||||||
|
@State private var isSending = false // Track sending state
|
||||||
|
|
||||||
@State private var cancellables = Set<AnyCancellable>() // For managing subscriptions
|
@State private var cancellables = Set<AnyCancellable>() // For managing subscriptions
|
||||||
|
|
||||||
@@ -315,7 +318,7 @@ struct ChatRoomView: View {
|
|||||||
scrollView.scrollTo(lastMessage.id, anchor: .bottom)
|
scrollView.scrollTo(lastMessage.id, anchor: .bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: messages.count) { _ in
|
.onChange(of: messages.count) { _, _ in
|
||||||
// Scroll to bottom when new messages arrive
|
// Scroll to bottom when new messages arrive
|
||||||
if let lastMessage = messages.last {
|
if let lastMessage = messages.last {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -325,6 +328,35 @@ struct ChatRoomView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Message input area
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
TextField("Send message...", text: $messageText)
|
||||||
|
.font(.system(size: 14))
|
||||||
|
.disabled(isSending)
|
||||||
|
.frame(height: 40)
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
await sendMessage()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if isSending {
|
||||||
|
ProgressView()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
} else {
|
||||||
|
Image(systemName: "arrow.up.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.buttonStyle(.glass)
|
||||||
|
.disabled(messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || isSending)
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top, 8)
|
||||||
}
|
}
|
||||||
.navigationTitle(room.name ?? "Chat")
|
.navigationTitle(room.name ?? "Chat")
|
||||||
.task {
|
.task {
|
||||||
@@ -351,11 +383,17 @@ struct ChatRoomView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func loadMessages() async {
|
private func loadMessages() async {
|
||||||
|
// Prevent reloading if already loaded
|
||||||
|
guard !hasLoadedMessages else { return }
|
||||||
|
|
||||||
guard let token = appState.token, let serverUrl = appState.serverUrl else {
|
guard let token = appState.token, let serverUrl = appState.serverUrl else {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoading = true
|
||||||
|
error = nil
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let messages = try await appState.networkService.fetchChatMessages(
|
let messages = try await appState.networkService.fetchChatMessages(
|
||||||
chatRoomId: room.id,
|
chatRoomId: room.id,
|
||||||
@@ -364,6 +402,7 @@ struct ChatRoomView: View {
|
|||||||
)
|
)
|
||||||
// Sort with newest messages first (for flipped list, newest will appear at bottom)
|
// Sort with newest messages first (for flipped list, newest will appear at bottom)
|
||||||
self.messages = messages.sorted { $0.createdAt < $1.createdAt }
|
self.messages = messages.sorted { $0.createdAt < $1.createdAt }
|
||||||
|
hasLoadedMessages = true
|
||||||
} catch {
|
} catch {
|
||||||
print("[watchOS] Error loading messages: \(error.localizedDescription)")
|
print("[watchOS] Error loading messages: \(error.localizedDescription)")
|
||||||
self.error = error
|
self.error = error
|
||||||
@@ -372,6 +411,66 @@ struct ChatRoomView: View {
|
|||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func sendMessage() async {
|
||||||
|
let content = messageText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
guard !content.isEmpty,
|
||||||
|
let token = appState.token,
|
||||||
|
let serverUrl = appState.serverUrl else { return }
|
||||||
|
|
||||||
|
isSending = true
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Generate a nonce for the message
|
||||||
|
let nonce = UUID().uuidString
|
||||||
|
|
||||||
|
// Prepare the request data
|
||||||
|
let messageData: [String: Any] = [
|
||||||
|
"content": content,
|
||||||
|
"attachments_id": [], // Empty for now, can be extended for attachments
|
||||||
|
"meta": [:],
|
||||||
|
"nonce": nonce
|
||||||
|
]
|
||||||
|
|
||||||
|
// Create the URL
|
||||||
|
guard let url = URL(string: "\(serverUrl)/sphere/chat/\(room.id)/messages") else {
|
||||||
|
throw URLError(.badURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the request
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
request.httpBody = try JSONSerialization.data(withJSONObject: messageData, options: [])
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
let (data, response) = try await URLSession.shared.data(for: request)
|
||||||
|
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse,
|
||||||
|
(200...299).contains(httpResponse.statusCode) else {
|
||||||
|
throw URLError(.badServerResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the response to get the sent message
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
decoder.dateDecodingStrategy = .iso8601
|
||||||
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
let sentMessage = try decoder.decode(SnChatMessage.self, from: data)
|
||||||
|
|
||||||
|
// Add the message to the local list
|
||||||
|
messages.append(sentMessage)
|
||||||
|
|
||||||
|
// Clear the input
|
||||||
|
messageText = ""
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
print("[watchOS] Error sending message: \(error.localizedDescription)")
|
||||||
|
// Could show an error alert here
|
||||||
|
}
|
||||||
|
|
||||||
|
isSending = false
|
||||||
|
}
|
||||||
|
|
||||||
private func sendReadReceipt() {
|
private func sendReadReceipt() {
|
||||||
let data: [String: Any] = ["chat_room_id": room.id]
|
let data: [String: Any] = ["chat_room_id": room.id]
|
||||||
let packet: [String: Any] = ["type": "messages.read", "data": data, "endpoint": "sphere"]
|
let packet: [String: Any] = ["type": "messages.read", "data": data, "endpoint": "sphere"]
|
||||||
@@ -487,12 +586,26 @@ struct ChatMessageItem: View {
|
|||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let content = message.content {
|
if let content = message.content, !content.isEmpty {
|
||||||
Text(content)
|
Text(content)
|
||||||
.font(.system(size: 14))
|
.font(.system(size: 14))
|
||||||
.lineLimit(nil)
|
.lineLimit(nil)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !message.attachments.isEmpty {
|
||||||
|
AttachmentView(attachment: message.attachments[0])
|
||||||
|
if message.attachments.count > 1 {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Image(systemName: "paperclip.circle.fill")
|
||||||
|
.frame(width: 12, height: 12)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
Text("\(message.attachments.count - 1)+ attachments")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, 4)
|
.padding(.vertical, 4)
|
||||||
|
|||||||
Reference in New Issue
Block a user