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 }