damus

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

commit dffb60a601a1da3fd0608829a4c754e5267f6fd2
parent df076b03fdde02c771dd4274a67138ffec879006
Author: William Casarin <jb55@jb55.com>
Date:   Sun,  5 Mar 2023 16:39:00 -0500

Immediately search for events and profiles

Instead of having to click twice

Changelog-Changed: Immediately search for events and profiles

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 20++++++++++++++++----
Mdamus/ContentView.swift | 33++++++++++++++++++++++++++++-----
Ddamus/Models/SearchedEventView.swift | 53-----------------------------------------------------
Adamus/Views/Search/SearchingEventView.swift | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adamus/Views/Search/SearchingProfileView.swift | 20++++++++++++++++++++
Mdamus/Views/SearchResultsView.swift | 141+++++++++++++++++++++++++++++++++++--------------------------------------------
6 files changed, 239 insertions(+), 140 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -171,7 +171,8 @@ 4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; }; 4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; }; 4CCEB7A929B29DD50078AA28 /* SearchResultsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7A829B29DD50078AA28 /* SearchResultsModel.swift */; }; - 4CCEB7AB29B2A1320078AA28 /* SearchedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AA29B2A1320078AA28 /* SearchedEventView.swift */; }; + 4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; }; + 4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; }; 4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; }; 4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; }; 4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; }; @@ -513,7 +514,8 @@ 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; }; 4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; }; 4CCEB7A829B29DD50078AA28 /* SearchResultsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsModel.swift; sourceTree = "<group>"; }; - 4CCEB7AA29B2A1320078AA28 /* SearchedEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SearchedEventView.swift; path = ../../Models/SearchedEventView.swift; sourceTree = "<group>"; }; + 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingEventView.swift; sourceTree = "<group>"; }; + 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; }; 4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; }; 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; }; 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; }; @@ -761,6 +763,7 @@ 4C75EFA227FA576C0006080F /* Views */ = { isa = PBXGroup; children = ( + 4CCEB7AC29B53D180078AA28 /* Search */, 4C30AC7029A5676F00E2BD5A /* Notifications */, 4CE0E2B029A3DF4700DB4CA2 /* Timeline */, 4CE879562996C44A00F758CC /* Zaps */, @@ -930,7 +933,6 @@ isa = PBXGroup; children = ( 4CC7AAEF297F11C700430951 /* SelectedEventView.swift */, - 4CCEB7AA29B2A1320078AA28 /* SearchedEventView.swift */, 4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */, 4CC7AAF3297F18B400430951 /* ReplyDescription.swift */, 4CC7AAF5297F1A6A00430951 /* EventBody.swift */, @@ -952,6 +954,15 @@ path = Search; sourceTree = "<group>"; }; + 4CCEB7AC29B53D180078AA28 /* Search */ = { + isa = PBXGroup; + children = ( + 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */, + 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */, + ); + path = Search; + sourceTree = "<group>"; + }; 4CE0E2B029A3DF4700DB4CA2 /* Timeline */ = { isa = PBXGroup; children = ( @@ -1388,6 +1399,7 @@ 4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */, 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */, 4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */, + 4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */, 4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */, 4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */, 4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */, @@ -1396,6 +1408,7 @@ 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */, + 4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */, BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */, 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */, 4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */, @@ -1408,7 +1421,6 @@ 4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */, 4C3EA67528FF7A5A00C48A62 /* take.c in Sources */, 4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */, - 4CCEB7AB29B2A1320078AA28 /* SearchedEventView.swift in Sources */, 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */, 4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */, 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */, diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -147,7 +147,7 @@ struct ContentView: View { search_open = false isSideBarOpened = false } - + var timelineNavItem: Text { switch selected_timeline { case .home: @@ -199,7 +199,7 @@ struct ContentView: View { EmptyView() } } - .navigationBarTitle(timelineNavItem, displayMode: .inline) + .navigationBarTitle(timeline_name(selected_timeline), displayMode: .inline) .toolbar { ToolbarItem(placement: .principal) { VStack { @@ -348,7 +348,7 @@ struct ContentView: View { active_profile = ref.ref_id profile_open = true } else if ref.key == "e" { - find_event(state: damus_state!, evid: ref.ref_id, find_from: nil) { ev in + find_event(state: damus_state!, evid: ref.ref_id, search_type: .event, find_from: nil) { ev in if let ev { active_event = ev } @@ -767,7 +767,7 @@ func setup_notifications() { } -func find_event(state: DamusState, evid: String, find_from: [String]?, callback: @escaping (NostrEvent?) -> ()) { +func find_event(state: DamusState, evid: String, search_type: SearchType, find_from: [String]?, callback: @escaping (NostrEvent?) -> ()) { if let ev = state.events.lookup(evid) { callback(ev) return @@ -776,7 +776,13 @@ func find_event(state: DamusState, evid: String, find_from: [String]?, callback: let subid = UUID().description var has_event = false - var filter = NostrFilter.filter_ids([ evid ]) + + var filter = search_type == .event ? NostrFilter.filter_ids([ evid ]) : NostrFilter.filter_authors([ evid ]) + + if search_type == .profile { + filter.kinds = [0] + } + filter.limit = 1 var attempts = 0 @@ -808,3 +814,20 @@ func find_event(state: DamusState, evid: String, find_from: [String]?, callback: } } + + +func timeline_name(_ timeline: Timeline?) -> String { + guard let timeline else { + return "" + } + switch timeline { + case .home: + return "Home" + case .notifications: + return "Notifications" + case .search: + return "Universe 🛸" + case .dms: + return "DMs" + } +} diff --git a/damus/Models/SearchedEventView.swift b/damus/Models/SearchedEventView.swift @@ -1,53 +0,0 @@ -// -// SearchedEventView.swift -// damus -// -// Created by William Casarin on 2023-03-03. -// - -import SwiftUI - -enum EventSearchState { - case searching - case not_found - case found(NostrEvent) -} - -struct SearchedEventView: View { - let state: DamusState - let event_id: String - @State var search_state: EventSearchState = .searching - - var body: some View { - Group { - switch search_state { - case .not_found: - Text("Event could not be found") - case .searching: - Text("Searching...") - case .found(let ev): - let thread = ThreadModel(event: ev, damus_state: state) - let dest = ThreadView(state: state, thread: thread) - NavigationLink(destination: dest) { - EventView(damus: state, event: ev) - } - .buttonStyle(.plain) - } - } - .onAppear { - find_event(state: state, evid: event_id, find_from: nil) { ev in - if let ev { - self.search_state = .found(ev) - } else { - self.search_state = .not_found - } - } - } - } -} - -struct SearchedEventView_Previews: PreviewProvider { - static var previews: some View { - SearchedEventView(state: test_damus_state(), event_id: "event_id") - } -} diff --git a/damus/Views/Search/SearchingEventView.swift b/damus/Views/Search/SearchingEventView.swift @@ -0,0 +1,112 @@ +// +// SearchingEventView.swift +// damus +// +// Created by William Casarin on 2023-03-05. +// + +import SwiftUI + +enum SearchState { + case searching + case found(NostrEvent) + case found_profile(String) + case not_found +} + +enum SearchType { + case event + case profile +} + +struct SearchingEventView: View { + let state: DamusState + let evid: String + let search_type: SearchType + @State var search_state: SearchState = .searching + + var bech32_evid: String { + guard let bytes = hex_decode(evid) else { + return evid + } + let noteid = bech32_encode(hrp: "note", bytes) + return abbrev_pubkey(noteid) + } + + var search_name: String { + switch search_type { + case .profile: + return "profile" + case .event: + return "note" + } + } + + var body: some View { + Group { + switch search_state { + case .searching: + HStack(spacing: 10) { + Text("Looking for \(search_name)...", comment: "Label that appears when searching for note or profile") + ProgressView() + .progressViewStyle(.circular) + } + case .found(let ev): + NavigationLink(destination: ThreadView(state: state, thread: ThreadModel(event: ev, damus_state: state))) { + + EventView(damus: state, event: ev) + } + .buttonStyle(PlainButtonStyle()) + case .found_profile(let pk): + NavigationLink(destination: ProfileView(damus_state: state, pubkey: pk)) { + + FollowUserView(target: .pubkey(pk), damus_state: state) + } + .buttonStyle(PlainButtonStyle()) + case .not_found: + Text("\(search_name.capitalized) not found", comment: "When a note or profile is not found when searching for it via its note id") + } + } + .onAppear { + + switch search_type { + case .event: + if let ev = state.events.lookup(evid) { + self.search_state = .found(ev) + return + } + case .profile: + if state.profiles.lookup(id: evid) != nil { + self.search_state = .found_profile(evid) + return + } + } + + find_event(state: state, evid: evid, search_type: search_type, find_from: nil) { ev in + + if let ev { + self.search_state = .found(ev) + } else { + self.search_state = .not_found + } + } + + } + } +} + +struct SearchingEventView_Previews: PreviewProvider { + static var previews: some View { + let state = test_damus_state() + SearchingEventView(state: state, evid: test_event.id, search_type: .event) + } +} + + +enum EventSearchState { + case searching + case not_found + case found(NostrEvent) + case found_profile(String) +} + diff --git a/damus/Views/Search/SearchingProfileView.swift b/damus/Views/Search/SearchingProfileView.swift @@ -0,0 +1,20 @@ +// +// SearchingProfileView.swift +// damus +// +// Created by William Casarin on 2023-03-05. +// + +import SwiftUI + +struct SearchingProfileView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct SearchingProfileView_Previews: PreviewProvider { + static var previews: some View { + SearchingProfileView() + } +} diff --git a/damus/Views/SearchResultsView.swift b/damus/Views/SearchResultsView.swift @@ -18,6 +18,7 @@ enum Search { struct SearchResultsView: View { let damus_state: DamusState @Binding var search: String + @State var result: Search? = nil func ProfileSearchResult(pk: String, res: Profile) -> some View { @@ -43,36 +44,23 @@ struct SearchResultsView: View { case .profile(let prof): let decoded = try? bech32_decode(prof) let hex = hex_encode(decoded!.data) - let prof_view = ProfileView(damus_state: damus_state, pubkey: hex) - NavigationLink(destination: prof_view) { - Text("Goto profile \(prof)", comment: "Navigation link to go to profile.") - } + + SearchingEventView(state: damus_state, evid: hex, search_type: .profile) case .hex(let h): - let prof_view = ProfileView(damus_state: damus_state, pubkey: h) + //let prof_view = ProfileView(damus_state: damus_state, pubkey: h) //let ev_view = ThreadView(damus: damus_state, event_id: h) - - NavigationLink(destination: prof_view) { - Text("Goto profile \(h)", comment: "Navigation link to go to profile referenced by hex code.") - } - /* - VStack(spacing: 50) { - NavigationLink(destination: ev_view) { - Text("Goto post \(h)", comment: "Navigation link to go to post referenced by hex code.") - } + + VStack(spacing: 10) { + SearchingEventView(state: damus_state, evid: h, search_type: .event) + + SearchingEventView(state: damus_state, evid: h, search_type: .profile) } - */ + case .note(let nid): - /* let decoded = try? bech32_decode(nid) let hex = hex_encode(decoded!.data) - let ev_view = ThreadView(state: state, ev: ev) - */ - Text("Todo: fix this") - /* - NavigationLink(destination: ev_view) { - Text("Goto post \(nid)", comment: "Navigation link to go to post referenced by note ID.") - } - */ + + SearchingEventView(state: damus_state, evid: hex, search_type: .event) case .none: Text("none", comment: "No search results.") } @@ -81,66 +69,14 @@ struct SearchResultsView: View { } } - func search_changed(_ new: String) { - guard new.count != 0 else { - return - } - - if new.first! == "#" { - let ht = String(new.dropFirst()) - self.result = .hashtag(ht) - return - } - - if hex_decode(new) != nil, new.count == 64 { - self.result = .hex(new) - return - } - - if new.starts(with: "npub") { - if (try? bech32_decode(new)) != nil { - self.result = .profile(new) - return - } - } - - if new.starts(with: "note") { - if (try? bech32_decode(new)) != nil { - self.result = .note(new) - return - } - } - - let profs = damus_state.profiles.profiles.enumerated() - let results: [(String, Profile)] = profs.reduce(into: []) { acc, els in - let pk = els.element.key - let prof = els.element.value.profile - let lowname = prof.name.map { $0.lowercased() } - let lownip05 = damus_state.profiles.is_validated(pk).map { $0.host.lowercased() } - let lowdisp = prof.display_name.map { $0.lowercased() } - let ok = new.count == 1 ? - ((lowname?.starts(with: new) ?? false) || - (lownip05?.starts(with: new) ?? false) || - (lowdisp?.starts(with: new) ?? false)) : (pk.starts(with: new) || String(new.dropFirst()) == pk - || lowname?.contains(new) ?? false - || lownip05?.contains(new) ?? false - || lowdisp?.contains(new) ?? false) - if ok { - acc.append((pk, prof)) - } - } - - self.result = .profiles(results) - } - var body: some View { MainContent .frame(maxHeight: .infinity) .onAppear { - search_changed(search) + self.result = search_changed(profiles: damus_state.profiles, search) } .onChange(of: search) { new in - search_changed(new) + self.result = search_changed(profiles: damus_state.profiles, new) } } } @@ -152,3 +88,52 @@ struct SearchResultsView_Previews: PreviewProvider { } } */ + + +func search_changed(profiles: Profiles, _ new: String) -> Search? { + guard new.count != 0 else { + return nil + } + + if new.first! == "#" { + let ht = String(new.dropFirst()) + return .hashtag(ht) + } + + if hex_decode(new) != nil, new.count == 64 { + return .hex(new) + } + + if new.starts(with: "npub") { + if (try? bech32_decode(new)) != nil { + return .profile(new) + } + } + + if new.starts(with: "note") { + if (try? bech32_decode(new)) != nil { + return .note(new) + } + } + + let profs = profiles.profiles.enumerated() + let results: [(String, Profile)] = profs.reduce(into: []) { acc, els in + let pk = els.element.key + let prof = els.element.value.profile + let lowname = prof.name.map { $0.lowercased() } + let lownip05 = profiles.is_validated(pk).map { $0.host.lowercased() } + let lowdisp = prof.display_name.map { $0.lowercased() } + let ok = new.count == 1 ? + ((lowname?.starts(with: new) ?? false) || + (lownip05?.starts(with: new) ?? false) || + (lowdisp?.starts(with: new) ?? false)) : (pk.starts(with: new) || String(new.dropFirst()) == pk + || lowname?.contains(new) ?? false + || lownip05?.contains(new) ?? false + || lowdisp?.contains(new) ?? false) + if ok { + acc.append((pk, prof)) + } + } + + return .profiles(results) +}