mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 23:45:18 +00:00
Feature models observe per-conversation objects directly: PublicChatModel forwards the active Conversation's objectWillChange, PrivateInboxModel republishes only for the selected peer's conversation - background appends no longer invalidate foreground views. LegacyConversationStore, the coalescing bridge, and IdentityResolver are deleted (resolver canonicalization proved display-invisible: nothing enumerates direct conversations, lookups are by exact peer ID, and raw keying is strictly more robust - documented as a design deviation). Selection state moves into the store. ChatViewModel.messages/privateChats survive as derived read views for coordinators that genuinely need them; hot paths use a new store-direct privateMessages(for:) witness. Final numbers vs pre-migration baselines: pipeline.privateIngest 9.7k -> 24.0k msg/s (2.5x) pipeline.publicIngest 6.8k -> 13.7k msg/s (2.0x) delivery updates 38k -> 117-133k/s Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
97 lines
3.1 KiB
Swift
97 lines
3.1 KiB
Swift
//
|
|
// CommandSuggestionsView.swift
|
|
// bitchat
|
|
//
|
|
// Created by Islam on 29/10/2025.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct CommandSuggestionsView: View {
|
|
@EnvironmentObject private var privateConversationModel: PrivateConversationModel
|
|
@EnvironmentObject private var locationChannelsModel: LocationChannelsModel
|
|
|
|
@Binding var messageText: String
|
|
|
|
let textColor: Color
|
|
let backgroundColor: Color
|
|
let secondaryTextColor: Color
|
|
|
|
private var filteredCommands: [CommandInfo] {
|
|
guard messageText.hasPrefix("/") && !messageText.contains(" ") else { return [] }
|
|
let isGeoPublic = locationChannelsModel.selectedChannel.isLocation
|
|
let isGeoDM = privateConversationModel.selectedPeerID?.isGeoDM == true
|
|
return CommandInfo.all(isGeoPublic: isGeoPublic, isGeoDM: isGeoDM).filter { command in
|
|
command.alias.starts(with: messageText.lowercased())
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
ForEach(filteredCommands) { command in
|
|
Button {
|
|
messageText = command.alias + " "
|
|
} label: {
|
|
buttonRow(for: command)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.background(Color.gray.opacity(0.1))
|
|
}
|
|
}
|
|
.background(backgroundColor)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 4)
|
|
.stroke(secondaryTextColor.opacity(0.3), lineWidth: 1)
|
|
)
|
|
}
|
|
|
|
private func buttonRow(for command: CommandInfo) -> some View {
|
|
HStack {
|
|
Text(command.alias)
|
|
.font(.bitchatSystem(size: 11, design: .monospaced))
|
|
.foregroundColor(textColor)
|
|
.fontWeight(.medium)
|
|
|
|
if let placeholder = command.placeholder {
|
|
Text(placeholder)
|
|
.font(.bitchatSystem(size: 10, design: .monospaced))
|
|
.foregroundColor(secondaryTextColor.opacity(0.8))
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Text(command.description)
|
|
.font(.bitchatSystem(size: 10, design: .monospaced))
|
|
.foregroundColor(secondaryTextColor)
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 3)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
}
|
|
|
|
@available(iOS 17, macOS 14, *)
|
|
#Preview {
|
|
@Previewable @State var messageText: String = "/"
|
|
let keychain = KeychainManager()
|
|
let viewModel = ChatViewModel(
|
|
keychain: keychain,
|
|
idBridge: NostrIdentityBridge(),
|
|
identityManager: SecureIdentityStateManager(keychain)
|
|
)
|
|
let privateConversationModel = PrivateConversationModel(
|
|
chatViewModel: viewModel,
|
|
conversations: viewModel.conversations
|
|
)
|
|
let locationChannelsModel = LocationChannelsModel()
|
|
|
|
CommandSuggestionsView(
|
|
messageText: $messageText,
|
|
textColor: .green,
|
|
backgroundColor: .primary,
|
|
secondaryTextColor: .secondary
|
|
)
|
|
.environmentObject(privateConversationModel)
|
|
.environmentObject(locationChannelsModel)
|
|
}
|