mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 14:45:22 +00:00
Refactor room commands and improve UX
- 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
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 63;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -76,10 +76,10 @@
|
|||||||
6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = "<group>"; };
|
6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = "<group>"; };
|
||||||
763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = "<group>"; };
|
763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = "<group>"; };
|
||||||
7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
822AA698BDB7BB939B62A010 /* BloomFilterTests.swift.disabled */ = {isa = PBXFileReference; lastKnownFileType = text; path = BloomFilterTests.swift.disabled; sourceTree = "<group>"; };
|
||||||
8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePaddingTests.swift; sourceTree = "<group>"; };
|
8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePaddingTests.swift; sourceTree = "<group>"; };
|
||||||
96CDF6D0AF0F2052A6C7E634 /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = "<group>"; };
|
96CDF6D0AF0F2052A6C7E634 /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = "<group>"; };
|
||||||
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 = "<group>"; };
|
A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = "<group>"; };
|
A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = "<group>"; };
|
||||||
AA4D7595A613F7ED3B386132 /* MessageRetryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRetryService.swift; sourceTree = "<group>"; };
|
AA4D7595A613F7ED3B386132 /* MessageRetryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRetryService.swift; sourceTree = "<group>"; };
|
||||||
@@ -284,7 +284,6 @@
|
|||||||
);
|
);
|
||||||
mainGroup = 18198ED912AAF495D8AF7763;
|
mainGroup = 18198ED912AAF495D8AF7763;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
preferredProjectObjectVersion = 54;
|
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
@@ -476,6 +475,7 @@
|
|||||||
CODE_SIGNING_REQUIRED = YES;
|
CODE_SIGNING_REQUIRED = YES;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = bitchat/Info.plist;
|
INFOPLIST_FILE = bitchat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
@@ -521,6 +521,7 @@
|
|||||||
CODE_SIGNING_REQUIRED = YES;
|
CODE_SIGNING_REQUIRED = YES;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = bitchat/Info.plist;
|
INFOPLIST_FILE = bitchat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
|||||||
@@ -1445,25 +1445,54 @@ extension ChatViewModel: BitchatDelegate {
|
|||||||
)
|
)
|
||||||
messages.append(systemMessage)
|
messages.append(systemMessage)
|
||||||
}
|
}
|
||||||
case "/list":
|
case "/rooms":
|
||||||
if joinedRooms.isEmpty {
|
// Discover all rooms (both joined and not joined)
|
||||||
|
var allRooms: Set<String> = 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(
|
let systemMessage = BitchatMessage(
|
||||||
sender: "system",
|
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(),
|
timestamp: Date(),
|
||||||
isRelay: false
|
isRelay: false
|
||||||
)
|
)
|
||||||
messages.append(systemMessage)
|
messages.append(systemMessage)
|
||||||
} else {
|
} else {
|
||||||
let roomList = joinedRooms.sorted().map { room in
|
let roomList = allRooms.sorted().map { room in
|
||||||
let isProtected = passwordProtectedRooms.contains(room) ? " 🔒" : ""
|
var status = ""
|
||||||
let isCreator = roomCreators[room] == meshService.myPeerID ? " (owner)" : ""
|
if joinedRooms.contains(room) {
|
||||||
return "\(room)\(isProtected)\(isCreator)"
|
status += " ✓"
|
||||||
|
}
|
||||||
|
if passwordProtectedRooms.contains(room) {
|
||||||
|
status += " 🔒"
|
||||||
|
}
|
||||||
|
if roomCreators[room] == meshService.myPeerID {
|
||||||
|
status += " (owner)"
|
||||||
|
}
|
||||||
|
return "\(room)\(status)"
|
||||||
}.joined(separator: "\n")
|
}.joined(separator: "\n")
|
||||||
|
|
||||||
let systemMessage = BitchatMessage(
|
let systemMessage = BitchatMessage(
|
||||||
sender: "system",
|
sender: "system",
|
||||||
content: "your rooms:\n\(roomList)",
|
content: "discovered rooms:\n\(roomList)\n\n✓ = joined",
|
||||||
timestamp: Date(),
|
timestamp: Date(),
|
||||||
isRelay: false
|
isRelay: false
|
||||||
)
|
)
|
||||||
@@ -1492,59 +1521,6 @@ extension ChatViewModel: BitchatDelegate {
|
|||||||
)
|
)
|
||||||
messages.append(systemMessage)
|
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":
|
case "/transfer":
|
||||||
// Transfer room ownership
|
// Transfer room ownership
|
||||||
let parts = command.split(separator: " ", maxSplits: 1).map(String.init)
|
let parts = command.split(separator: " ", maxSplits: 1).map(String.init)
|
||||||
@@ -1598,12 +1574,12 @@ extension ChatViewModel: BitchatDelegate {
|
|||||||
messages.removeAll()
|
messages.removeAll()
|
||||||
bitchatLog("cleared main chat", category: "chat")
|
bitchatLog("cleared main chat", category: "chat")
|
||||||
}
|
}
|
||||||
case "/favorite", "/fav":
|
case "/save":
|
||||||
// Toggle favorite status for current room
|
// Toggle save status for current room
|
||||||
guard let room = currentRoom else {
|
guard let room = currentRoom else {
|
||||||
let systemMessage = BitchatMessage(
|
let systemMessage = BitchatMessage(
|
||||||
sender: "system",
|
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(),
|
timestamp: Date(),
|
||||||
isRelay: false
|
isRelay: false
|
||||||
)
|
)
|
||||||
@@ -1612,7 +1588,7 @@ extension ChatViewModel: BitchatDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let isFavorite = MessageRetentionService.shared.toggleFavoriteRoom(room)
|
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 just marked as favorite, load any previously saved messages
|
||||||
if isFavorite {
|
if isFavorite {
|
||||||
@@ -1634,7 +1610,7 @@ extension ChatViewModel: BitchatDelegate {
|
|||||||
|
|
||||||
let systemMessage = BitchatMessage(
|
let systemMessage = BitchatMessage(
|
||||||
sender: "system",
|
sender: "system",
|
||||||
content: "room \(room) \(status) favorites. loaded \(savedMessages.count) saved messages.",
|
content: "room \(room) \(status). loaded \(savedMessages.count) saved messages.",
|
||||||
timestamp: Date(),
|
timestamp: Date(),
|
||||||
isRelay: false
|
isRelay: false
|
||||||
)
|
)
|
||||||
@@ -1642,7 +1618,7 @@ extension ChatViewModel: BitchatDelegate {
|
|||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
let systemMessage = BitchatMessage(
|
||||||
sender: "system",
|
sender: "system",
|
||||||
content: "room \(room) \(status) favorites.",
|
content: "room \(room) \(status).",
|
||||||
timestamp: Date(),
|
timestamp: Date(),
|
||||||
isRelay: false
|
isRelay: false
|
||||||
)
|
)
|
||||||
@@ -1651,7 +1627,7 @@ extension ChatViewModel: BitchatDelegate {
|
|||||||
} else {
|
} else {
|
||||||
let systemMessage = BitchatMessage(
|
let systemMessage = BitchatMessage(
|
||||||
sender: "system",
|
sender: "system",
|
||||||
content: "room \(room) \(status) favorites.",
|
content: "room \(room) \(status).",
|
||||||
timestamp: Date(),
|
timestamp: Date(),
|
||||||
isRelay: false
|
isRelay: false
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -70,9 +70,6 @@ struct AppInfoView: View {
|
|||||||
|
|
||||||
FeatureRow(icon: "lock.fill", title: "Password Rooms",
|
FeatureRow(icon: "lock.fill", title: "Password Rooms",
|
||||||
description: "Secure rooms with passwords and AES encryption")
|
description: "Secure rooms with passwords and AES encryption")
|
||||||
|
|
||||||
FeatureRow(icon: "mic.fill", title: "Voice Notes",
|
|
||||||
description: "Push-to-talk voice messages with compression")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Privacy
|
// Privacy
|
||||||
@@ -117,7 +114,6 @@ struct AppInfoView: View {
|
|||||||
Text("Battery: Adaptive scanning based on level")
|
Text("Battery: Adaptive scanning based on level")
|
||||||
Text("Platform: Universal (iOS, iPadOS, macOS)")
|
Text("Platform: Universal (iOS, iPadOS, macOS)")
|
||||||
Text("Rooms: Password-protected with key commitments")
|
Text("Rooms: Password-protected with key commitments")
|
||||||
Text("Commands: /j /m /w /list /pass /favorite /discover")
|
|
||||||
Text("Storage: Keychain for passwords, encrypted retention")
|
Text("Storage: Keychain for passwords, encrypted retention")
|
||||||
}
|
}
|
||||||
.font(.system(size: 14, design: .monospaced))
|
.font(.system(size: 14, design: .monospaced))
|
||||||
@@ -203,9 +199,6 @@ struct AppInfoView: View {
|
|||||||
|
|
||||||
FeatureRow(icon: "lock.fill", title: "Password Rooms",
|
FeatureRow(icon: "lock.fill", title: "Password Rooms",
|
||||||
description: "Secure rooms with passwords and AES encryption")
|
description: "Secure rooms with passwords and AES encryption")
|
||||||
|
|
||||||
FeatureRow(icon: "mic.fill", title: "Voice Notes",
|
|
||||||
description: "Push-to-talk voice messages with compression")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Privacy
|
// Privacy
|
||||||
@@ -250,7 +243,6 @@ struct AppInfoView: View {
|
|||||||
Text("Battery: Adaptive scanning based on level")
|
Text("Battery: Adaptive scanning based on level")
|
||||||
Text("Platform: Universal (iOS, iPadOS, macOS)")
|
Text("Platform: Universal (iOS, iPadOS, macOS)")
|
||||||
Text("Rooms: Password-protected with key commitments")
|
Text("Rooms: Password-protected with key commitments")
|
||||||
Text("Commands: /j /m /w /list /pass /favorite /discover")
|
|
||||||
Text("Storage: Keychain for passwords, encrypted retention")
|
Text("Storage: Keychain for passwords, encrypted retention")
|
||||||
}
|
}
|
||||||
.font(.system(size: 14, design: .monospaced))
|
.font(.system(size: 14, design: .monospaced))
|
||||||
|
|||||||
@@ -506,18 +506,24 @@ struct ContentView: View {
|
|||||||
// Command suggestions
|
// Command suggestions
|
||||||
if showCommandSuggestions && !commandSuggestions.isEmpty {
|
if showCommandSuggestions && !commandSuggestions.isEmpty {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
let commandDescriptions = [
|
let baseCommands: [String: String] = [
|
||||||
"/j": "join or create a room",
|
"/j": "join or create a room",
|
||||||
"/list": "show your joined rooms",
|
"/rooms": "show all discovered rooms",
|
||||||
"/w": "see who's online",
|
"/w": "see who's online",
|
||||||
"/m": "send private message",
|
"/m": "send private message",
|
||||||
"/clear": "clear chat messages",
|
"/clear": "clear chat messages"
|
||||||
|
]
|
||||||
|
|
||||||
|
let roomCommands: [String: String] = [
|
||||||
"/transfer": "transfer room ownership",
|
"/transfer": "transfer room ownership",
|
||||||
"/pass": "change room password",
|
"/pass": "change room password",
|
||||||
"/favorite": "toggle room retention",
|
"/save": "save room messages locally"
|
||||||
"/discover": "find active rooms"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
let commandDescriptions = viewModel.currentRoom != nil
|
||||||
|
? baseCommands.merging(roomCommands) { (_, new) in new }
|
||||||
|
: baseCommands
|
||||||
|
|
||||||
ForEach(commandSuggestions, id: \.self) { command in
|
ForEach(commandSuggestions, id: \.self) { command in
|
||||||
Button(action: {
|
Button(action: {
|
||||||
// Replace current text with selected command
|
// Replace current text with selected command
|
||||||
@@ -590,17 +596,22 @@ struct ContentView: View {
|
|||||||
|
|
||||||
// Check for command autocomplete
|
// Check for command autocomplete
|
||||||
if newValue.hasPrefix("/") && newValue.count >= 1 {
|
if newValue.hasPrefix("/") && newValue.count >= 1 {
|
||||||
let commandDescriptions = [
|
// Build context-aware command list
|
||||||
|
var commandDescriptions = [
|
||||||
("/j", "join or create a room"),
|
("/j", "join or create a room"),
|
||||||
("/list", "show your joined rooms"),
|
("/rooms", "show all discovered rooms"),
|
||||||
("/w", "see who's online"),
|
("/w", "see who's online"),
|
||||||
("/m", "send private message"),
|
("/m", "send private message"),
|
||||||
("/clear", "clear chat messages"),
|
("/clear", "clear chat messages")
|
||||||
("/transfer", "transfer room ownership"),
|
|
||||||
("/pass", "change room password"),
|
|
||||||
("/favorite", "toggle room retention"),
|
|
||||||
("/discover", "find active rooms")
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 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()
|
let input = newValue.lowercased()
|
||||||
commandSuggestions = commandDescriptions
|
commandSuggestions = commandDescriptions
|
||||||
.filter { $0.0.starts(with: input) }
|
.filter { $0.0.starts(with: input) }
|
||||||
|
|||||||
Reference in New Issue
Block a user