damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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