From 41c2899008d052823cb349d12763e5120dbc1510 Mon Sep 17 00:00:00 2001 From: jack Date: Sat, 5 Jul 2025 18:45:32 +0200 Subject: [PATCH] Refactor room commands and improve UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace /list with /rooms showing all discovered rooms with join status - Remove /discover command (merged into /rooms) - Rename /favorite to /save for clarity - Make room-specific commands (/transfer, /pass, /save) only appear in room context - Remove voice notes from app info (feature doesn't exist) - Remove Commands line from app info screen - Show checkmark (āœ“) for joined rooms in /rooms output --- bitchat.xcodeproj/project.pbxproj | 9 +- bitchat/ViewModels/ChatViewModel.swift | 112 ++++++++++--------------- bitchat/Views/AppInfoView.swift | 8 -- bitchat/Views/ContentView.swift | 35 +++++--- 4 files changed, 72 insertions(+), 92 deletions(-) diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index fe370041..c46a10a8 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 63; objects = { /* Begin PBXBuildFile section */ @@ -76,10 +76,10 @@ 6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = ""; }; 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = ""; }; 7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 822AA698BDB7BB939B62A010 /* BloomFilterTests.swift.disabled */ = {isa = PBXFileReference; path = BloomFilterTests.swift.disabled; sourceTree = ""; }; + 822AA698BDB7BB939B62A010 /* BloomFilterTests.swift.disabled */ = {isa = PBXFileReference; lastKnownFileType = text; path = BloomFilterTests.swift.disabled; sourceTree = ""; }; 8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePaddingTests.swift; sourceTree = ""; }; 96CDF6D0AF0F2052A6C7E634 /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = ""; }; - 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = ""; }; AA4D7595A613F7ED3B386132 /* MessageRetryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRetryService.swift; sourceTree = ""; }; @@ -284,7 +284,6 @@ ); mainGroup = 18198ED912AAF495D8AF7763; minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 54; projectDirPath = ""; projectRoot = ""; targets = ( @@ -476,6 +475,7 @@ CODE_SIGNING_REQUIRED = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -521,6 +521,7 @@ CODE_SIGNING_REQUIRED = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index ad2bbf83..48e02d76 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -1445,25 +1445,54 @@ extension ChatViewModel: BitchatDelegate { ) messages.append(systemMessage) } - case "/list": - if joinedRooms.isEmpty { + case "/rooms": + // Discover all rooms (both joined and not joined) + var allRooms: Set = Set() + + // Add joined rooms + allRooms.formUnion(joinedRooms) + + // Find rooms from messages we've seen + for msg in messages { + if let room = msg.room { + allRooms.insert(room) + } + } + + // Also check room messages we've cached + for (room, _) in roomMessages { + allRooms.insert(room) + } + + // Add password protected rooms we know about + allRooms.formUnion(passwordProtectedRooms) + + if allRooms.isEmpty { let systemMessage = BitchatMessage( sender: "system", - content: "you haven't joined any rooms yet. use /create or /join to get started.", + content: "no rooms discovered yet. rooms appear as people use them.", timestamp: Date(), isRelay: false ) messages.append(systemMessage) } else { - let roomList = joinedRooms.sorted().map { room in - let isProtected = passwordProtectedRooms.contains(room) ? " šŸ”’" : "" - let isCreator = roomCreators[room] == meshService.myPeerID ? " (owner)" : "" - return "\(room)\(isProtected)\(isCreator)" + let roomList = allRooms.sorted().map { room in + var status = "" + if joinedRooms.contains(room) { + status += " āœ“" + } + if passwordProtectedRooms.contains(room) { + status += " šŸ”’" + } + if roomCreators[room] == meshService.myPeerID { + status += " (owner)" + } + return "\(room)\(status)" }.joined(separator: "\n") let systemMessage = BitchatMessage( sender: "system", - content: "your rooms:\n\(roomList)", + content: "discovered rooms:\n\(roomList)\n\nāœ“ = joined", timestamp: Date(), isRelay: false ) @@ -1492,59 +1521,6 @@ extension ChatViewModel: BitchatDelegate { ) messages.append(systemMessage) } - case "/discover": - // Discover public rooms - var publicRooms: [String] = [] - var protectedRooms: [String] = [] - - // Find rooms from messages we've seen - for msg in messages { - if let room = msg.room { - if passwordProtectedRooms.contains(room) { - if !protectedRooms.contains(room) { - protectedRooms.append(room) - } - } else { - if !publicRooms.contains(room) { - publicRooms.append(room) - } - } - } - } - - // Also check room messages we've cached - for (room, _) in roomMessages { - if passwordProtectedRooms.contains(room) { - if !protectedRooms.contains(room) { - protectedRooms.append(room) - } - } else { - if !publicRooms.contains(room) { - publicRooms.append(room) - } - } - } - - var discoveryMessage = "" - if publicRooms.isEmpty && protectedRooms.isEmpty { - discoveryMessage = "no rooms discovered yet. rooms appear as people use them." - } else { - if !publicRooms.isEmpty { - discoveryMessage += "public rooms:\n" + publicRooms.sorted().joined(separator: ", ") - } - if !protectedRooms.isEmpty { - if !discoveryMessage.isEmpty { discoveryMessage += "\n\n" } - discoveryMessage += "protected rooms:\n" + protectedRooms.sorted().map { "\($0) šŸ”’" }.joined(separator: ", ") - } - } - - let systemMessage = BitchatMessage( - sender: "system", - content: discoveryMessage, - timestamp: Date(), - isRelay: false - ) - messages.append(systemMessage) case "/transfer": // Transfer room ownership let parts = command.split(separator: " ", maxSplits: 1).map(String.init) @@ -1598,12 +1574,12 @@ extension ChatViewModel: BitchatDelegate { messages.removeAll() bitchatLog("cleared main chat", category: "chat") } - case "/favorite", "/fav": - // Toggle favorite status for current room + case "/save": + // Toggle save status for current room guard let room = currentRoom else { let systemMessage = BitchatMessage( sender: "system", - content: "you must be in a room to mark it as favorite.", + content: "you must be in a room to save it.", timestamp: Date(), isRelay: false ) @@ -1612,7 +1588,7 @@ extension ChatViewModel: BitchatDelegate { } let isFavorite = MessageRetentionService.shared.toggleFavoriteRoom(room) - let status = isFavorite ? "added to" : "removed from" + let status = isFavorite ? "saved" : "unsaved" // If just marked as favorite, load any previously saved messages if isFavorite { @@ -1634,7 +1610,7 @@ extension ChatViewModel: BitchatDelegate { let systemMessage = BitchatMessage( sender: "system", - content: "room \(room) \(status) favorites. loaded \(savedMessages.count) saved messages.", + content: "room \(room) \(status). loaded \(savedMessages.count) saved messages.", timestamp: Date(), isRelay: false ) @@ -1642,7 +1618,7 @@ extension ChatViewModel: BitchatDelegate { } else { let systemMessage = BitchatMessage( sender: "system", - content: "room \(room) \(status) favorites.", + content: "room \(room) \(status).", timestamp: Date(), isRelay: false ) @@ -1651,7 +1627,7 @@ extension ChatViewModel: BitchatDelegate { } else { let systemMessage = BitchatMessage( sender: "system", - content: "room \(room) \(status) favorites.", + content: "room \(room) \(status).", timestamp: Date(), isRelay: false ) diff --git a/bitchat/Views/AppInfoView.swift b/bitchat/Views/AppInfoView.swift index bd66b048..a2d1e7f6 100644 --- a/bitchat/Views/AppInfoView.swift +++ b/bitchat/Views/AppInfoView.swift @@ -70,9 +70,6 @@ struct AppInfoView: View { FeatureRow(icon: "lock.fill", title: "Password Rooms", description: "Secure rooms with passwords and AES encryption") - - FeatureRow(icon: "mic.fill", title: "Voice Notes", - description: "Push-to-talk voice messages with compression") } // Privacy @@ -117,7 +114,6 @@ struct AppInfoView: View { Text("Battery: Adaptive scanning based on level") Text("Platform: Universal (iOS, iPadOS, macOS)") Text("Rooms: Password-protected with key commitments") - Text("Commands: /j /m /w /list /pass /favorite /discover") Text("Storage: Keychain for passwords, encrypted retention") } .font(.system(size: 14, design: .monospaced)) @@ -203,9 +199,6 @@ struct AppInfoView: View { FeatureRow(icon: "lock.fill", title: "Password Rooms", description: "Secure rooms with passwords and AES encryption") - - FeatureRow(icon: "mic.fill", title: "Voice Notes", - description: "Push-to-talk voice messages with compression") } // Privacy @@ -250,7 +243,6 @@ struct AppInfoView: View { Text("Battery: Adaptive scanning based on level") Text("Platform: Universal (iOS, iPadOS, macOS)") Text("Rooms: Password-protected with key commitments") - Text("Commands: /j /m /w /list /pass /favorite /discover") Text("Storage: Keychain for passwords, encrypted retention") } .font(.system(size: 14, design: .monospaced)) diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 5488f427..947bd2fd 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -506,18 +506,24 @@ struct ContentView: View { // Command suggestions if showCommandSuggestions && !commandSuggestions.isEmpty { VStack(alignment: .leading, spacing: 0) { - let commandDescriptions = [ + let baseCommands: [String: String] = [ "/j": "join or create a room", - "/list": "show your joined rooms", + "/rooms": "show all discovered rooms", "/w": "see who's online", "/m": "send private message", - "/clear": "clear chat messages", + "/clear": "clear chat messages" + ] + + let roomCommands: [String: String] = [ "/transfer": "transfer room ownership", "/pass": "change room password", - "/favorite": "toggle room retention", - "/discover": "find active rooms" + "/save": "save room messages locally" ] + let commandDescriptions = viewModel.currentRoom != nil + ? baseCommands.merging(roomCommands) { (_, new) in new } + : baseCommands + ForEach(commandSuggestions, id: \.self) { command in Button(action: { // Replace current text with selected command @@ -590,17 +596,22 @@ struct ContentView: View { // Check for command autocomplete if newValue.hasPrefix("/") && newValue.count >= 1 { - let commandDescriptions = [ + // Build context-aware command list + var commandDescriptions = [ ("/j", "join or create a room"), - ("/list", "show your joined rooms"), + ("/rooms", "show all discovered rooms"), ("/w", "see who's online"), ("/m", "send private message"), - ("/clear", "clear chat messages"), - ("/transfer", "transfer room ownership"), - ("/pass", "change room password"), - ("/favorite", "toggle room retention"), - ("/discover", "find active rooms") + ("/clear", "clear chat messages") ] + + // Add room-specific commands if in a room + if viewModel.currentRoom != nil { + commandDescriptions.append(("/transfer", "transfer room ownership")) + commandDescriptions.append(("/pass", "change room password")) + commandDescriptions.append(("/save", "save room messages locally")) + } + let input = newValue.lowercased() commandSuggestions = commandDescriptions .filter { $0.0.starts(with: input) }