@@ -43,8 +43,6 @@ struct ContentView: View {
4343 @State private var showPeerList = false
4444 @State private var showSidebar = false
4545 @State private var showAppInfo = false
46- @State private var showCommandSuggestions = false
47- @State private var commandSuggestions : [ String ] = [ ]
4846 @State private var showMessageActions = false
4947 @State private var selectedMessageSender : String ?
5048 @State private var selectedMessageSenderID : PeerID ?
@@ -605,77 +603,12 @@ struct ContentView: View {
605603 . padding ( . horizontal, 12 )
606604 }
607605
608- // Command suggestions
609- if showCommandSuggestions && !commandSuggestions. isEmpty {
610- VStack ( alignment: . leading, spacing: 0 ) {
611- // Define commands with aliases and syntax
612- let baseInfo : [ ( commands: [ String ] , syntax: String ? , description: String ) ] = [
613- ( [ " /block " ] , " [nickname] " , " block or list blocked peers " ) ,
614- ( [ " /clear " ] , nil , " clear chat messages " ) ,
615- ( [ " /hug " ] , " <nickname> " , " send someone a warm hug " ) ,
616- ( [ " /m " , " /msg " ] , " <nickname> [message] " , " send private message " ) ,
617- ( [ " /slap " ] , " <nickname> " , " slap someone with a trout " ) ,
618- ( [ " /unblock " ] , " <nickname> " , " unblock a peer " ) ,
619- ( [ " /w " ] , nil , " see who's online " )
620- ]
621- let isGeoPublic : Bool = { if case . location = locationManager. selectedChannel { return true } ; return false } ( )
622- let isGeoDM = viewModel. selectedPrivateChatPeer? . isGeoDM == true
623- let favInfo : [ ( commands: [ String ] , syntax: String ? , description: String ) ] = [
624- ( [ " /fav " ] , " <nickname> " , " add to favorites " ) ,
625- ( [ " /unfav " ] , " <nickname> " , " remove from favorites " )
626- ]
627- let commandInfo = baseInfo + ( ( isGeoPublic || isGeoDM) ? [ ] : favInfo)
628-
629- // Build the display
630- let allCommands = commandInfo
631-
632- // Show matching commands
633- ForEach ( commandSuggestions, id: \. self) { command in
634- // Find the command info for this suggestion
635- if let info = allCommands. first ( where: { $0. commands. contains ( command) } ) {
636- Button ( action: {
637- // Replace current text with selected command
638- messageText = command + " "
639- showCommandSuggestions = false
640- commandSuggestions = [ ]
641- } ) {
642- HStack {
643- // Show all aliases together
644- Text ( info. commands. joined ( separator: " , " ) )
645- . font ( . bitchatSystem( size: 11 , design: . monospaced) )
646- . foregroundColor ( textColor)
647- . fontWeight ( . medium)
648-
649- // Show syntax if any
650- if let syntax = info. syntax {
651- Text ( syntax)
652- . font ( . bitchatSystem( size: 10 , design: . monospaced) )
653- . foregroundColor ( secondaryTextColor. opacity ( 0.8 ) )
654- }
655-
656- Spacer ( )
657-
658- // Show description
659- Text ( info. description)
660- . font ( . bitchatSystem( size: 10 , design: . monospaced) )
661- . foregroundColor ( secondaryTextColor)
662- }
663- . padding ( . horizontal, 12 )
664- . padding ( . vertical, 3 )
665- . frame ( maxWidth: . infinity, alignment: . leading)
666- }
667- . buttonStyle ( . plain)
668- . background ( Color . gray. opacity ( 0.1 ) )
669- }
670- }
671- }
672- . background ( backgroundColor)
673- . overlay (
674- RoundedRectangle ( cornerRadius: 4 )
675- . stroke ( secondaryTextColor. opacity ( 0.3 ) , lineWidth: 1 )
676- )
677- . padding ( . horizontal, 12 )
678- }
606+ CommandSuggestionsView (
607+ messageText: $messageText,
608+ textColor: textColor,
609+ backgroundColor: backgroundColor,
610+ secondaryTextColor: secondaryTextColor
611+ )
679612
680613 // Recording indicator
681614 if isPreparingVoiceNote || isRecordingVoiceNote {
@@ -709,68 +642,11 @@ struct ContentView: View {
709642 )
710643 . frame ( maxWidth: . infinity, alignment: . leading)
711644 . onChange ( of: messageText) { newValue in
712- // Cancel previous debounce timer
713645 autocompleteDebounceTimer? . invalidate ( )
714-
715- // Debounce autocomplete updates to reduce calls during rapid typing
716646 autocompleteDebounceTimer = Timer . scheduledTimer ( withTimeInterval: 0.15 , repeats: false ) { _ in
717- // Get cursor position (approximate - end of text for now)
718647 let cursorPosition = newValue. count
719648 viewModel. updateAutocomplete ( for: newValue, cursorPosition: cursorPosition)
720649 }
721-
722- // Check for command autocomplete (instant, no debounce needed)
723- if newValue. hasPrefix ( " / " ) && newValue. count >= 1 {
724- // Build context-aware command list
725- let isGeoPublic : Bool = {
726- if case . location = locationManager. selectedChannel { return true }
727- return false
728- } ( )
729- let isGeoDM = viewModel. selectedPrivateChatPeer? . isGeoDM == true
730- var commandDescriptions = [
731- ( " /block " , String ( localized: " content.commands.block " , comment: " Description for /block command " ) ) ,
732- ( " /clear " , String ( localized: " content.commands.clear " , comment: " Description for /clear command " ) ) ,
733- ( " /hug " , String ( localized: " content.commands.hug " , comment: " Description for /hug command " ) ) ,
734- ( " /m " , String ( localized: " content.commands.message " , comment: " Description for /m command " ) ) ,
735- ( " /slap " , String ( localized: " content.commands.slap " , comment: " Description for /slap command " ) ) ,
736- ( " /unblock " , String ( localized: " content.commands.unblock " , comment: " Description for /unblock command " ) ) ,
737- ( " /w " , String ( localized: " content.commands.who " , comment: " Description for /w command " ) )
738- ]
739- // Only show favorites commands when not in geohash context
740- if !( isGeoPublic || isGeoDM) {
741- commandDescriptions. append ( ( " /fav " , String ( localized: " content.commands.favorite " , comment: " Description for /fav command " ) ) )
742- commandDescriptions. append ( ( " /unfav " , String ( localized: " content.commands.unfavorite " , comment: " Description for /unfav command " ) ) )
743- }
744-
745- let input = newValue. lowercased ( )
746-
747- // Map of aliases to primary commands
748- let aliases : [ String : String ] = [
749- " /join " : " /j " ,
750- " /msg " : " /m "
751- ]
752-
753- // Filter commands, but convert aliases to primary
754- commandSuggestions = commandDescriptions
755- . filter { $0. 0 . starts ( with: input) }
756- . map { $0. 0 }
757-
758- // Also check if input matches an alias
759- for (alias, primary) in aliases {
760- if alias. starts ( with: input) && !commandSuggestions. contains ( primary) {
761- if commandDescriptions. contains ( where: { $0. 0 == primary } ) {
762- commandSuggestions. append ( primary)
763- }
764- }
765- }
766-
767- // Remove duplicates and sort
768- commandSuggestions = Array ( Set ( commandSuggestions) ) . sorted ( )
769- showCommandSuggestions = !commandSuggestions. isEmpty
770- } else {
771- showCommandSuggestions = false
772- commandSuggestions = [ ]
773- }
774650 }
775651
776652 HStack ( alignment: . center, spacing: 4 ) {
@@ -787,7 +663,7 @@ struct ContentView: View {
787663 . padding ( . bottom, 8 )
788664 . background ( backgroundColor. opacity ( 0.95 ) )
789665 }
790-
666+
791667 private func handleOpenURL( _ url: URL ) {
792668 guard url. scheme == " bitchat " else { return }
793669 switch url. host {
0 commit comments