ui: add ndb search to universe view

This PR adds the NDB search functionality from the pull down search in the
posting timeline to the universe view.

Changelog-Added: Added NDB search functionality to the universe view

Signed-off-by: ericholguin <ericholguin@apache.org>
This commit is contained in:
ericholguin
2024-09-11 20:34:24 -06:00
parent 379de6ff8e
commit 62772615b6
4 changed files with 146 additions and 7 deletions

View File

@@ -37,6 +37,7 @@ enum Route: Hashable {
case Reactions(reactions: EventsModel)
case Zaps(target: ZapTarget)
case Search(search: SearchModel)
case NDBSearch(results: Binding<[NostrEvent]>)
case EULA
case Login
case CreateAccount
@@ -105,6 +106,8 @@ enum Route: Hashable {
ZapsView(state: damusState, target: target)
case .Search(let search):
SearchView(appstate: damusState, search: search)
case .NDBSearch(let results):
NDBSearchView(damus_state: damusState, results: results)
case .EULA:
EULAView(nav: navigationCoordinator)
case .Login:
@@ -200,6 +203,8 @@ enum Route: Hashable {
case .Search(let search):
hasher.combine("search")
hasher.combine(search.search)
case .NDBSearch(let results):
hasher.combine("results")
case .EULA:
hasher.combine("eula")
case .Login:

View File

@@ -0,0 +1,51 @@
//
// NDBSearchView.swift
// damus
//
// Created by eric on 9/9/24.
//
import SwiftUI
struct NDBSearchView: View {
let damus_state: DamusState
@Binding var results: [NostrEvent]
var body: some View {
ScrollView {
if results.count > 0 {
HStack {
Spacer()
Image("search")
Text("Top hits", comment: "A label indicating that the notes being displayed below it are all top note search results")
Spacer()
}
.padding()
.foregroundColor(.secondary)
ForEach(results, id: \.self) { note in
EventView(damus: damus_state, event: note)
.onTapGesture {
let event = note.get_inner_event(cache: damus_state.events) ?? note
let thread = ThreadModel(event: event, damus_state: damus_state)
damus_state.nav.push(route: Route.Thread(thread: thread))
}
.padding(.horizontal)
ThiccDivider()
}
} else if results.count == 0 {
HStack {
Spacer()
Image("search")
Text("No results", comment: "A label indicating that note search resulted in no results")
Spacer()
}
.padding()
.foregroundColor(.secondary)
}
}
}
}

View File

@@ -8,6 +8,7 @@
import SwiftUI
struct MultiSearch {
let text: String
let hashtag: String
let profiles: [Pubkey]
}
@@ -43,6 +44,7 @@ enum Search: Identifiable {
struct InnerSearchResults: View {
let damus_state: DamusState
let search: Search?
@Binding var results: [NostrEvent]
func ProfileSearchResult(pk: Pubkey) -> some View {
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
@@ -51,7 +53,35 @@ struct InnerSearchResults: View {
func HashtagSearch(_ ht: String) -> some View {
let search_model = SearchModel(state: damus_state, search: .filter_hashtag([ht]))
return NavigationLink(value: Route.Search(search: search_model)) {
Text("Search hashtag: #\(ht)", comment: "Navigation link to search hashtag.")
HStack {
Image("search")
Text("#\(ht)", comment: "Navigation link to search hashtag.")
}
.padding(.horizontal, 15)
.padding(.vertical, 5)
.background(DamusColors.neutral1)
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(DamusColors.neutral3, lineWidth: 1)
)
}
}
func TextSearch(_ txt: String) -> some View {
return NavigationLink(value: Route.NDBSearch(results: $results)) {
HStack {
Image("search")
Text("Notes", comment: "Navigation link to search text.")
}
.padding(.horizontal, 15)
.padding(.vertical, 5)
.background(DamusColors.neutral1)
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(DamusColors.neutral3, lineWidth: 1)
)
}
}
@@ -88,8 +118,11 @@ struct InnerSearchResults: View {
case .naddr(let naddr):
SearchingEventView(state: damus_state, search_type: .naddr(naddr))
case .multi(let multi):
VStack {
HashtagSearch(multi.hashtag)
VStack(alignment: .leading) {
HStack {
HashtagSearch(multi.hashtag)
TextSearch(multi.text)
}
ProfilesSearch(multi.profiles)
}
@@ -104,10 +137,47 @@ struct SearchResultsView: View {
let damus_state: DamusState
@Binding var search: String
@State var result: Search? = nil
@State var results: [NostrEvent] = []
let debouncer: Debouncer = Debouncer(interval: 0.25)
func do_search(query: String) {
let limit = 16
var note_keys = damus_state.ndb.text_search(query: query, limit: limit, order: .newest_first)
var res = [NostrEvent]()
// TODO: fix duplicate results from search
var keyset = Set<NoteKey>()
// try reverse because newest first is a bit buggy on partial searches
if note_keys.count == 0 {
// don't touch existing results if there are no new ones
return
}
do {
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
for note_key in note_keys {
guard let note = damus_state.ndb.lookup_note_by_key_with_txn(note_key, txn: txn) else {
continue
}
if !keyset.contains(note_key) {
let owned_note = note.to_owned()
res.append(owned_note)
keyset.insert(note_key)
}
}
}
let res_ = res
Task { @MainActor [res_] in
results = res_
}
}
var body: some View {
ScrollView {
InnerSearchResults(damus_state: damus_state, search: result)
InnerSearchResults(damus_state: damus_state, search: result, results: $results)
.padding()
}
.frame(maxHeight: .infinity)
@@ -119,6 +189,13 @@ struct SearchResultsView: View {
guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return }
self.result = search_for_string(profiles: damus_state.profiles, contacts: damus_state.contacts, search: search, txn: txn)
}
.onChange(of: search) { query in
debouncer.debounce {
Task.detached {
do_search(query: query)
}
}
}
}
}
@@ -174,7 +251,7 @@ func search_for_string<Y>(profiles: Profiles, contacts: Contacts, search new: St
return .naddr(naddr)
}
let multisearch = MultiSearch(hashtag: make_hashtagable(searchQuery), profiles: search_profiles(profiles: profiles, contacts: contacts, search: new, txn: txn))
let multisearch = MultiSearch(text: new, hashtag: make_hashtagable(searchQuery), profiles: search_profiles(profiles: profiles, contacts: contacts, search: new, txn: txn))
return .multi(multisearch)
}