From ab90d244b58ef835b00e8aa461797d1eea059707 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 31 Oct 2025 00:39:06 +0800 Subject: [PATCH] :sparkles: Able to send message on watchOS --- .../Views/ChatViews.swift | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/ios/WatchRunner Watch App/Views/ChatViews.swift b/ios/WatchRunner Watch App/Views/ChatViews.swift index fd04d826..0f398e2f 100644 --- a/ios/WatchRunner Watch App/Views/ChatViews.swift +++ b/ios/WatchRunner Watch App/Views/ChatViews.swift @@ -261,6 +261,8 @@ struct ChatRoomView: View { @State private var error: Error? @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() // For managing subscriptions @@ -316,7 +318,7 @@ struct ChatRoomView: View { scrollView.scrollTo(lastMessage.id, anchor: .bottom) } } - .onChange(of: messages.count) { _ in + .onChange(of: messages.count) { _, _ in // Scroll to bottom when new messages arrive if let lastMessage = messages.last { withAnimation { @@ -326,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") .task { @@ -380,6 +411,66 @@ struct ChatRoomView: View { 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() { let data: [String: Any] = ["chat_room_id": room.id] let packet: [String: Any] = ["type": "messages.read", "data": data, "endpoint": "sphere"]