damus

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

PullDownSearch.swift (4337B)


      1 //
      2 //  PullDownSearch.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2023-12-03.
      6 //
      7 
      8 import Foundation
      9 
     10 import SwiftUI
     11 
     12 struct PullDownSearchView: View {
     13     @State private var search_text = ""
     14     @State private var results: [NostrEvent] = []
     15     @State private var is_active: Bool = false
     16     let debouncer: Debouncer = Debouncer(interval: 0.25)
     17     let state: DamusState
     18     let on_cancel: () -> Void
     19     
     20     func do_search(query: String) {
     21         let limit = 16
     22         var note_keys = state.ndb.text_search(query: query, limit: limit, order: .newest_first)
     23         var res = [NostrEvent]()
     24         // TODO: fix duplicate results from search
     25         var keyset = Set<NoteKey>()
     26 
     27         // try reverse because newest first is a bit buggy on partial searches
     28         if note_keys.count == 0 {
     29             // don't touch existing results if there are no new ones
     30             return
     31         }
     32 
     33         do {
     34             guard let txn = NdbTxn(ndb: state.ndb) else { return }
     35             for note_key in note_keys {
     36                 guard let note = state.ndb.lookup_note_by_key_with_txn(note_key, txn: txn) else {
     37                     continue
     38                 }
     39 
     40                 if !keyset.contains(note_key) {
     41                     let owned_note = note.to_owned()
     42                     res.append(owned_note)
     43                     keyset.insert(note_key)
     44                 }
     45             }
     46         }
     47 
     48         let res_ = res
     49 
     50         Task { @MainActor [res_] in
     51             results = res_
     52         }
     53     }
     54 
     55     var body: some View {
     56         VStack(alignment: .leading) {
     57             HStack {
     58                 TextField(NSLocalizedString("Search", comment: "Title of the text field for searching."), text: $search_text)
     59                     .textFieldStyle(RoundedBorderTextFieldStyle())
     60                     .onChange(of: search_text) { query in
     61                         debouncer.debounce {
     62                             Task.detached {
     63                                 do_search(query: query)
     64                             }
     65                         }
     66                     }
     67                     .onTapGesture {
     68                         is_active = true
     69                     }
     70 
     71                 if is_active {
     72                     Button(action: {
     73                         search_text = ""
     74                         results = []
     75                         end_editing()
     76                         on_cancel()
     77                     }, label: {
     78                         Text("Cancel", comment: "Button to cancel out of search text entry mode.")
     79                     })
     80                 }
     81             }
     82             .padding()
     83 
     84             if results.count > 0 {
     85                 HStack {
     86                     Image("search")
     87                     Text("Top hits", comment: "A label indicating that the notes being displayed below it are all top note search results")
     88                     Spacer()
     89                 }
     90                 .padding(.horizontal)
     91                 .foregroundColor(.secondary)
     92 
     93                 ForEach(results, id: \.self) { note in
     94                     EventView(damus: state, event: note)
     95                         .onTapGesture {
     96                             let event = note.get_inner_event(cache: state.events) ?? note
     97                             let thread = ThreadModel(event: event, damus_state: state)
     98                             state.nav.push(route: Route.Thread(thread: thread))
     99                         }
    100                 }
    101                 
    102                 HStack {
    103                     Image("notes.fill")
    104                     Text("Notes", comment: "A label indicating that the notes being displayed below it are from a timeline, not search results")
    105                     Spacer()
    106                 }
    107                 .foregroundColor(.secondary)
    108                 .padding(.horizontal)
    109             } else if results.count == 0 && !search_text.isEmpty {
    110                 HStack {
    111                     Image("search")
    112                     Text("No results", comment: "A label indicating that note search resulted in no results")
    113                     Spacer()
    114                 }
    115                 .padding(.horizontal)
    116                 .foregroundColor(.secondary)
    117             }
    118         }
    119     }
    120 }
    121 
    122 struct PullDownSearchView_Previews: PreviewProvider {
    123     static var previews: some View {
    124         PullDownSearchView(state: test_damus_state, on_cancel: {})
    125     }
    126 }