SearchResultsView.swift (6436B)
1 // 2 // SearchResultsView.swift 3 // damus 4 // 5 // Created by William Casarin on 2022-06-06. 6 // 7 8 import SwiftUI 9 10 struct MultiSearch { 11 let hashtag: String 12 let profiles: [Pubkey] 13 } 14 15 enum Search: Identifiable { 16 case profiles([Pubkey]) 17 case hashtag(String) 18 case profile(Pubkey) 19 case note(NoteId) 20 case nip05(String) 21 case hex(Data) 22 case multi(MultiSearch) 23 case nevent(NEvent) 24 case naddr(NAddr) 25 case nprofile(NProfile) 26 27 var id: String { 28 switch self { 29 case .profiles: return "profiles" 30 case .hashtag: return "hashtag" 31 case .profile: return "profile" 32 case .note: return "note" 33 case .nip05: return "nip05" 34 case .hex: return "hex" 35 case .multi: return "multi" 36 case .nevent: return "nevent" 37 case .naddr: return "naddr" 38 case .nprofile: return "nprofile" 39 } 40 } 41 } 42 43 struct InnerSearchResults: View { 44 let damus_state: DamusState 45 let search: Search? 46 47 func ProfileSearchResult(pk: Pubkey) -> some View { 48 FollowUserView(target: .pubkey(pk), damus_state: damus_state) 49 } 50 51 func HashtagSearch(_ ht: String) -> some View { 52 let search_model = SearchModel(state: damus_state, search: .filter_hashtag([ht])) 53 return NavigationLink(value: Route.Search(search: search_model)) { 54 Text("Search hashtag: #\(ht)", comment: "Navigation link to search hashtag.") 55 } 56 } 57 58 func ProfilesSearch(_ results: [Pubkey]) -> some View { 59 return LazyVStack { 60 ForEach(results, id: \.id) { pk in 61 ProfileSearchResult(pk: pk) 62 } 63 } 64 } 65 66 var body: some View { 67 Group { 68 switch search { 69 case .profiles(let results): 70 ProfilesSearch(results) 71 case .hashtag(let ht): 72 HashtagSearch(ht) 73 case .nip05(let addr): 74 SearchingEventView(state: damus_state, search_type: .nip05(addr)) 75 case .profile(let pubkey): 76 SearchingEventView(state: damus_state, search_type: .profile(pubkey)) 77 case .hex(let h): 78 VStack(spacing: 10) { 79 SearchingEventView(state: damus_state, search_type: .event(NoteId(h))) 80 SearchingEventView(state: damus_state, search_type: .profile(Pubkey(h))) 81 } 82 case .note(let nid): 83 SearchingEventView(state: damus_state, search_type: .event(nid)) 84 case .nevent(let nevent): 85 SearchingEventView(state: damus_state, search_type: .event(nevent.noteid)) 86 case .nprofile(let nprofile): 87 SearchingEventView(state: damus_state, search_type: .profile(nprofile.author)) 88 case .naddr(let naddr): 89 SearchingEventView(state: damus_state, search_type: .naddr(naddr)) 90 case .multi(let multi): 91 VStack { 92 HashtagSearch(multi.hashtag) 93 ProfilesSearch(multi.profiles) 94 } 95 96 case .none: 97 Text("none", comment: "No search results.") 98 } 99 } 100 } 101 } 102 103 struct SearchResultsView: View { 104 let damus_state: DamusState 105 @Binding var search: String 106 @State var result: Search? = nil 107 108 var body: some View { 109 ScrollView { 110 InnerSearchResults(damus_state: damus_state, search: result) 111 .padding() 112 } 113 .frame(maxHeight: .infinity) 114 .onAppear { 115 guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return } 116 self.result = search_for_string(profiles: damus_state.profiles, search: search, txn: txn) 117 } 118 .onChange(of: search) { new in 119 guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return } 120 self.result = search_for_string(profiles: damus_state.profiles, search: search, txn: txn) 121 } 122 } 123 } 124 125 /* 126 struct SearchResultsView_Previews: PreviewProvider { 127 static var previews: some View { 128 SearchResultsView(damus_state: test_damus_state(), s) 129 } 130 } 131 */ 132 133 134 func search_for_string<Y>(profiles: Profiles, search new: String, txn: NdbTxn<Y>) -> Search? { 135 guard new.count != 0 else { 136 return nil 137 } 138 139 let splitted = new.split(separator: "@") 140 141 if splitted.count == 2 { 142 return .nip05(new) 143 } 144 145 if new.first! == "#" { 146 return .hashtag(make_hashtagable(new)) 147 } 148 149 let searchQuery = remove_nostr_uri_prefix(new) 150 151 if let new = hex_decode_id(searchQuery) { 152 return .hex(new) 153 } 154 155 if searchQuery.starts(with: "npub") { 156 if let decoded = bech32_pubkey_decode(searchQuery) { 157 return .profile(decoded) 158 } 159 } 160 161 if searchQuery.starts(with: "note"), let decoded = try? bech32_decode(searchQuery) { 162 return .note(NoteId(decoded.data)) 163 } 164 165 if searchQuery.starts(with: "nevent"), case let .nevent(nevent) = Bech32Object.parse(searchQuery) { 166 return .nevent(nevent) 167 } 168 169 if searchQuery.starts(with: "nprofile"), case let .nprofile(nprofile) = Bech32Object.parse(searchQuery) { 170 return .nprofile(nprofile) 171 } 172 173 if searchQuery.starts(with: "naddr"), case let .naddr(naddr) = Bech32Object.parse(searchQuery) { 174 return .naddr(naddr) 175 } 176 177 let multisearch = MultiSearch(hashtag: make_hashtagable(searchQuery), profiles: search_profiles(profiles: profiles, search: new, txn: txn)) 178 return .multi(multisearch) 179 } 180 181 func make_hashtagable(_ str: String) -> String { 182 var new = str 183 guard str.utf8.count > 0 else { 184 return str 185 } 186 187 if new.hasPrefix("#") { 188 new = String(new.dropFirst()) 189 } 190 191 return String(new.filter{$0 != " "}) 192 } 193 194 func search_profiles<Y>(profiles: Profiles, search: String, txn: NdbTxn<Y>) -> [Pubkey] { 195 // Search by hex pubkey. 196 if let pubkey = hex_decode_pubkey(search), 197 profiles.lookup_key_by_pubkey(pubkey) != nil 198 { 199 return [pubkey] 200 } 201 202 // Search by npub pubkey. 203 if search.starts(with: "npub"), 204 let bech32_key = decode_bech32_key(search), 205 case Bech32Key.pub(let pk) = bech32_key, 206 profiles.lookup_key_by_pubkey(pk) != nil 207 { 208 return [pk] 209 } 210 211 let new = search.lowercased() 212 213 return profiles.search(search, limit: 10, txn: txn) 214 } 215