diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 97e2ec56..f833cfaa 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -39,6 +39,7 @@ class ChatViewModel: ObservableObject { @Published var currentRoom: String? = nil // Currently selected room @Published var roomMessages: [String: [BitchatMessage]] = [:] // room -> messages @Published var unreadRoomMessages: [String: Int] = [:] // room -> unread count + @Published var roomMembers: [String: Set] = [:] // room -> set of peer IDs who have sent messages let meshService = BluetoothMeshService() private let userDefaults = UserDefaults.standard @@ -132,8 +133,10 @@ class ChatViewModel: ObservableObject { currentRoom = nil } - // Keep messages for now (could clear if desired) + // Clean up room data unreadRoomMessages.removeValue(forKey: room) + roomMessages.removeValue(forKey: room) + roomMembers.removeValue(forKey: room) } func switchToRoom(_ room: String?) { @@ -229,6 +232,9 @@ class ChatViewModel: ObservableObject { timestamp: Date(), isRelay: false, originalSender: nil, + isPrivate: false, + recipientNickname: nil, + senderPeerID: meshService.myPeerID, mentions: mentions.isEmpty ? nil : mentions, room: messageRoom ) @@ -239,6 +245,12 @@ class ChatViewModel: ObservableObject { roomMessages[room] = [] } roomMessages[room]?.append(message) + + // Track ourselves as a room member + if roomMembers[room] == nil { + roomMembers[room] = [] + } + roomMembers[room]?.insert(meshService.myPeerID) } else { // Add to main messages messages.append(message) @@ -658,6 +670,14 @@ extension ChatViewModel: BitchatDelegate { roomMessages[room]?.append(message) roomMessages[room]?.sort { $0.timestamp < $1.timestamp } + // Track room members + if roomMembers[room] == nil { + roomMembers[room] = [] + } + if let senderPeerID = message.senderPeerID { + roomMembers[room]?.insert(senderPeerID) + } + // Update unread count if not currently viewing this room if currentRoom != room { unreadRoomMessages[room] = (unreadRoomMessages[room] ?? 0) + 1 diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 27373770..4459b4f9 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -212,6 +212,23 @@ struct ContentView: View { Spacer() + // Leave room button + Button(action: { + viewModel.leaveRoom(currentRoom) + }) { + Text("leave") + .font(.system(size: 12, design: .monospaced)) + .foregroundColor(Color.red) + .padding(.horizontal, 8) + .padding(.vertical, 2) + .overlay( + RoundedRectangle(cornerRadius: 4) + .stroke(Color.red.opacity(0.5), lineWidth: 1) + ) + } + .buttonStyle(.plain) + + // Back to main button Button(action: { viewModel.switchToRoom(nil) }) { @@ -272,7 +289,15 @@ struct ContentView: View { } let otherPeersCount = viewModel.connectedPeers.filter { $0 != viewModel.meshService.myPeerID }.count - Text(viewModel.isConnected ? "\(otherPeersCount) \(otherPeersCount == 1 ? "person" : "people")" : "alone :/") + let roomCount = viewModel.joinedRooms.count + let statusText = if !viewModel.isConnected { + "alone :/" + } else if roomCount > 0 { + "\(otherPeersCount) \(otherPeersCount == 1 ? "person" : "people") / \(roomCount) \(roomCount == 1 ? "room" : "rooms")" + } else { + "\(otherPeersCount) \(otherPeersCount == 1 ? "person" : "people")" + } + Text(statusText) .font(.system(size: 12, design: .monospaced)) .foregroundColor(viewModel.isConnected ? textColor : Color.red) } @@ -524,7 +549,13 @@ struct ContentView: View { // People section VStack(alignment: .leading, spacing: 8) { - if !viewModel.connectedPeers.isEmpty { + // Show appropriate header based on context + if let currentRoom = viewModel.currentRoom { + Text("IN \(currentRoom.uppercased())") + .font(.system(size: 11, weight: .semibold, design: .monospaced)) + .foregroundColor(secondaryTextColor) + .padding(.horizontal, 12) + } else if !viewModel.connectedPeers.isEmpty { Text("PEOPLE") .font(.system(size: 11, weight: .semibold, design: .monospaced)) .foregroundColor(secondaryTextColor) @@ -536,13 +567,29 @@ struct ContentView: View { .font(.system(size: 14, design: .monospaced)) .foregroundColor(secondaryTextColor) .padding(.horizontal) + } else if let currentRoom = viewModel.currentRoom, + (viewModel.roomMembers[currentRoom]?.isEmpty ?? true) { + Text("No one in this room yet") + .font(.system(size: 14, design: .monospaced)) + .foregroundColor(secondaryTextColor) + .padding(.horizontal) } else { let peerNicknames = viewModel.meshService.getPeerNicknames() let peerRSSI = viewModel.meshService.getPeerRSSI() let myPeerID = viewModel.meshService.myPeerID + // Filter peers based on current room + let peersToShow = if let currentRoom = viewModel.currentRoom, + let roomMemberIDs = viewModel.roomMembers[currentRoom] { + // Show only peers who have sent messages to this room + viewModel.connectedPeers.filter { roomMemberIDs.contains($0) && $0 != myPeerID } + } else { + // Show all connected peers in main chat + viewModel.connectedPeers.filter { $0 != myPeerID } + } + // Sort peers: favorites first, then alphabetically by nickname - let sortedPeers = viewModel.connectedPeers.filter { $0 != myPeerID }.sorted { peer1, peer2 in + let sortedPeers = peersToShow.sorted { peer1, peer2 in let isFav1 = viewModel.isFavorite(peerID: peer1) let isFav2 = viewModel.isFavorite(peerID: peer2)