commit 505ce0bd392a5f8a17f445b9fb7bd6571d44e102
parent 0fae54a98df16d7bb04751ee54ac052f2f6b6eb0
Author: William Casarin <jb55@jb55.com>
Date: Thu, 13 Jul 2023 11:10:53 -0700
Add the ability to follow hashtags
Changelog-Added: Add the ability to follow hashtags
Diffstat:
7 files changed, 407 insertions(+), 93 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -144,6 +144,7 @@
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */; };
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = 4C649880286E0EE300EAE2B3 /* secp256k1 */; };
4C687C212A5F7ED00092C550 /* DamusBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C687C202A5F7ED00092C550 /* DamusBackground.swift */; };
+ 4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C687C232A5FA86D0092C550 /* SearchHeaderView.swift */; };
4C687C272A6039500092C550 /* TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C687C262A6039500092C550 /* TestData.swift */; };
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C5132A4437C10062CAC0 /* ZapUserView.swift */; };
4C75EFA427FA577B0006080F /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; };
@@ -619,6 +620,7 @@
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesView.swift; sourceTree = "<group>"; };
4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesModel.swift; sourceTree = "<group>"; };
4C687C202A5F7ED00092C550 /* DamusBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusBackground.swift; sourceTree = "<group>"; };
+ 4C687C232A5FA86D0092C550 /* SearchHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHeaderView.swift; sourceTree = "<group>"; };
4C687C262A6039500092C550 /* TestData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestData.swift; sourceTree = "<group>"; };
4C73C5132A4437C10062CAC0 /* ZapUserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapUserView.swift; sourceTree = "<group>"; };
4C75EFA327FA577B0006080F /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
@@ -1099,6 +1101,14 @@
path = Notifications;
sourceTree = "<group>";
};
+ 4C687C2A2A6058450092C550 /* Search */ = {
+ isa = PBXGroup;
+ children = (
+ 4C687C232A5FA86D0092C550 /* SearchHeaderView.swift */,
+ );
+ path = Search;
+ sourceTree = "<group>";
+ };
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
@@ -1404,6 +1414,7 @@
4CE4F9DF285287A000C00DD9 /* Components */ = {
isa = PBXGroup;
children = (
+ 4C687C2A2A6058450092C550 /* Search */,
4C7D09702A0AEF4C00943473 /* Gradients */,
31D2E846295218AF006D67F8 /* Shimmer.swift */,
4CD7641A28A1641400B6928F /* EndBlock.swift */,
@@ -1946,6 +1957,7 @@
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */,
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */,
+ 4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */,
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
diff --git a/damus/Components/Search/SearchHeaderView.swift b/damus/Components/Search/SearchHeaderView.swift
@@ -0,0 +1,134 @@
+//
+// SearchIconView.swift
+// damus
+//
+// Created by William Casarin on 2023-07-12.
+//
+
+import SwiftUI
+
+struct SearchHeaderView: View {
+ let state: DamusState
+ let described: DescribedSearch
+ @State var is_following: Bool
+
+ init(state: DamusState, described: DescribedSearch) {
+ self.state = state
+ self.described = described
+
+ let is_following = (described.is_hashtag.map {
+ ht in is_following_hashtag(contacts: state.contacts.event, hashtag: ht)
+ }) ?? false
+
+ self._is_following = State(wrappedValue: is_following)
+ }
+
+ var Icon: some View {
+ ZStack {
+ Circle()
+ .fill(Color(red: 0xF8/255.0, green: 0xE7/255.0, blue: 0xF8/255.0))
+ .frame(width: 54, height: 54)
+
+ switch described {
+ case .hashtag:
+ Text("#")
+ .font(.largeTitle.bold())
+ .foregroundStyle(PinkGradient)
+ .mask(Text("#")
+ .font(.largeTitle.bold()))
+
+ case .unknown:
+ Image(systemName: "magnifyingglass")
+ .font(.title.bold())
+ .foregroundStyle(PinkGradient)
+ }
+ }
+ }
+
+ var SearchText: Text {
+ switch described {
+ case .hashtag(let ht):
+ Text(verbatim: "#" + ht)
+ case .unknown:
+ Text("Search")
+ }
+ }
+
+ func unfollow(_ hashtag: String) {
+ is_following = false
+ handle_unfollow(state: state, unfollow: .t(hashtag))
+ }
+
+ func follow(_ hashtag: String) {
+ is_following = true
+ handle_follow(state: state, follow: .t(hashtag))
+ }
+
+ func FollowButton(_ ht: String) -> some View {
+ return Button(action: { follow(ht) }) {
+ Text("Follow hashtag")
+ .font(.footnote.bold())
+ }
+ .buttonStyle(GradientButtonStyle(padding: 10))
+ }
+
+ func UnfollowButton(_ ht: String) -> some View {
+ return Button(action: { unfollow(ht) }) {
+ Text("Unfollow hashtag")
+ .font(.footnote.bold())
+ }
+ .buttonStyle(GradientButtonStyle(padding: 10))
+ }
+
+ var body: some View {
+ HStack(alignment: .center, spacing: 30) {
+ Icon
+
+ VStack(alignment: .leading, spacing: 10.0) {
+ SearchText
+ .foregroundStyle(DamusLogoGradient.gradient)
+ .font(.title.bold())
+
+ if state.is_privkey_user, case .hashtag(let ht) = described {
+ if is_following {
+ UnfollowButton(ht)
+ } else {
+ FollowButton(ht)
+ }
+ }
+ }
+ }
+ .onReceive(handle_notify(.followed)) { notif in
+ let ref = notif.object as! ReferencedId
+ guard hashtag_matches_search(desc: self.described, ref: ref) else { return }
+ self.is_following = true
+ }
+ .onReceive(handle_notify(.unfollowed)) { notif in
+ let ref = notif.object as! ReferencedId
+ guard hashtag_matches_search(desc: self.described, ref: ref) else { return }
+ self.is_following = false
+ }
+ }
+}
+
+func hashtag_matches_search(desc: DescribedSearch, ref: ReferencedId) -> Bool {
+ guard let ht = desc.is_hashtag, ref.key == "t" && ref.ref_id == ht
+ else { return false }
+ return true
+}
+
+func is_following_hashtag(contacts: NostrEvent?, hashtag: String) -> Bool {
+ guard let contacts else { return false }
+ return is_already_following(contacts: contacts, follow: .t(hashtag))
+}
+
+
+struct SearchHeaderView_Previews: PreviewProvider {
+ static var previews: some View {
+ VStack(alignment: .leading) {
+ SearchHeaderView(state: test_damus_state(), described: .hashtag("damus"))
+
+ SearchHeaderView(state: test_damus_state(), described: .unknown)
+ }
+ }
+}
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -395,16 +395,22 @@ struct ContentView: View {
}
}
.onReceive(handle_notify(.unfollow)) { notif in
- guard let state = self.damus_state else {
- return
- }
- handle_unfollow(state: state, notif: notif)
+ guard let state = self.damus_state else { return }
+ guard let unfollow = handle_unfollow_notif(state: state, notif: notif) else { return }
+ }
+ .onReceive(handle_notify(.unfollowed)) { notif in
+ guard let state = self.damus_state else { return }
+ let unfollow = notif.object as! ReferencedId
+ home.resubscribe(.unfollowing(unfollow))
}
.onReceive(handle_notify(.follow)) { notif in
- guard let state = self.damus_state else {
- return
- }
- handle_follow(state: state, notif: notif)
+ guard let state = self.damus_state else { return }
+ guard handle_follow_notif(state: state, notif: notif) else { return }
+ }
+ .onReceive(handle_notify(.followed)) { notif in
+ guard let state = self.damus_state else { return }
+ let follow = notif.object as! ReferencedId
+ home.resubscribe(.following)
}
.onReceive(handle_notify(.post)) { notif in
guard let state = self.damus_state,
@@ -879,47 +885,75 @@ func timeline_name(_ timeline: Timeline?) -> String {
}
}
-func handle_unfollow(state: DamusState, notif: Notification) {
+@discardableResult
+func handle_unfollow(state: DamusState, unfollow: ReferencedId) -> Bool {
guard let keypair = state.keypair.to_full() else {
- return
+ return false
}
-
- let target = notif.object as! FollowTarget
- let pk = target.pubkey
+
let old_contacts = state.contacts.event
- guard let ev = unfollow_reference(postbox: state.postbox, our_contacts: old_contacts, keypair: keypair, unfollow: .p(pk))
- else { return }
+ guard let ev = unfollow_reference(postbox: state.postbox, our_contacts: old_contacts, keypair: keypair, unfollow: unfollow)
+ else {
+ return false
+ }
- notify(.unfollowed, pk)
+ notify(.unfollowed, unfollow)
state.contacts.event = ev
- state.contacts.remove_friend(pk)
- state.user_search_cache.updateOwnContactsPetnames(id: state.pubkey, oldEvent: old_contacts, newEvent: ev)
+
+ if unfollow.key == "p" {
+ state.contacts.remove_friend(unfollow.ref_id)
+ state.user_search_cache.updateOwnContactsPetnames(id: state.pubkey, oldEvent: old_contacts, newEvent: ev)
+ }
+
+ return true
}
-func handle_follow(state: DamusState, notif: Notification) {
- guard let keypair = state.keypair.to_full() else {
- return
+func handle_unfollow_notif(state: DamusState, notif: Notification) -> ReferencedId? {
+ let target = notif.object as! FollowTarget
+ let pk = target.pubkey
+
+ let ref = ReferencedId.p(pk)
+ if handle_unfollow(state: state, unfollow: ref) {
+ return ref
}
- let fnotify = notif.object as! FollowTarget
+ return nil
+}
+
+@discardableResult
+func handle_follow(state: DamusState, follow: ReferencedId) -> Bool {
+ guard let keypair = state.keypair.to_full() else {
+ return false
+ }
- guard let ev = follow_reference(box: state.postbox, our_contacts: state.contacts.event, keypair: keypair, follow: .p(fnotify.pubkey))
+ guard let ev = follow_reference(box: state.postbox, our_contacts: state.contacts.event, keypair: keypair, follow: follow)
else {
- return
+ return false
}
- notify(.followed, fnotify.pubkey)
+ notify(.followed, follow)
state.contacts.event = ev
+ if follow.key == "p" {
+ state.contacts.add_friend_pubkey(follow.ref_id)
+ }
+ return true
+}
+
+@discardableResult
+func handle_follow_notif(state: DamusState, notif: Notification) -> Bool {
+ let fnotify = notif.object as! FollowTarget
switch fnotify {
case .pubkey(let pk):
state.contacts.add_friend_pubkey(pk)
case .contact(let ev):
state.contacts.add_friend_contact(ev)
}
+
+ return handle_follow(state: state, follow: .p(fnotify.pubkey))
}
func handle_post_notification(keypair: FullKeypair, postbox: PostBox, events: EventCache, notif: Notification) -> Bool {
diff --git a/damus/Models/Contacts.swift b/damus/Models/Contacts.swift
@@ -66,10 +66,19 @@ class Contacts {
}
}
- func get_friend_list() -> [String] {
- return Array(friends)
+ func get_friend_list() -> Set<String> {
+ return friends
}
-
+
+ func get_followed_hashtags() -> Set<String> {
+ guard let ev = self.event else { return Set() }
+ return ev.tags.reduce(into: Set<String>(), { htags, tag in
+ if tag.count >= 2 && tag[0] == "t" && tag[1] != "" {
+ htags.insert(tag[1])
+ }
+ })
+ }
+
func add_friend_pubkey(_ pubkey: String) {
friends.insert(pubkey)
}
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -23,6 +23,40 @@ struct NewEventsBits: OptionSet {
static let notifications: NewEventsBits = [.zaps, .likes, .reposts, .mentions]
}
+enum Resubscribe {
+ case following
+ case unfollowing(ReferencedId)
+}
+
+enum HomeResubFilter {
+ case pubkey(String)
+ case hashtag(String)
+
+ init?(from: ReferencedId) {
+ if from.key == "p" {
+ self = .pubkey(from.ref_id)
+ return
+ } else if from.key == "t" {
+ self = .hashtag(from.ref_id)
+ return
+ }
+
+ return nil
+ }
+
+ func filter(contacts: Contacts, ev: NostrEvent) -> Bool {
+ switch self {
+ case .pubkey(let pk):
+ return ev.pubkey == pk
+ case .hashtag(let ht):
+ if contacts.is_friend(ev.pubkey) {
+ return false
+ }
+ return ev.references(id: ht, key: "t")
+ }
+ }
+}
+
class HomeModel {
// Don't trigger a user notification for events older than a certain age
static let event_max_age_for_notification: TimeInterval = 12 * 60 * 60
@@ -36,6 +70,7 @@ class HomeModel {
var done_init: Bool = false
var incoming_dms: [NostrEvent] = []
let dm_debouncer = Debouncer(interval: 0.5)
+ let resub_debouncer = Debouncer(interval: 3.0)
var should_debounce_dms = true
let home_subid = UUID().description
@@ -90,6 +125,31 @@ class HomeModel {
}
}
+ func resubscribe(_ resubbing: Resubscribe) {
+ if self.should_debounce_dms {
+ // don't resub on initial load
+ return
+ }
+
+ print("hit resub debouncer")
+
+ resub_debouncer.debounce {
+ print("resub")
+ self.unsubscribe_to_home_filters()
+
+ switch resubbing {
+ case .following:
+ break
+ case .unfollowing(let r):
+ if let filter = HomeResubFilter(from: r) {
+ self.events.filter { ev in !filter.filter(contacts: self.damus_state.contacts, ev: ev) }
+ }
+ }
+
+ self.subscribe_to_home_filters()
+ }
+ }
+
func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
return
@@ -382,8 +442,7 @@ class HomeModel {
// TODO: since times should be based on events from a specific relay
// perhaps we could mark this in the relay pool somehow
- var friends = damus_state.contacts.get_friend_list()
- friends.append(damus_state.pubkey)
+ let friends = get_friends()
var contacts_filter = NostrFilter(kinds: [.metadata])
contacts_filter.authors = friends
@@ -405,18 +464,6 @@ class HomeModel {
dms_filter.pubkeys = [ damus_state.pubkey ]
our_dms_filter.authors = [ damus_state.pubkey ]
- // TODO: separate likes?
- var home_filter_kinds: [NostrKind] = [
- .text, .longform, .boost
- ]
- if !damus_state.settings.onlyzaps_mode {
- home_filter_kinds.append(.like)
- }
- var home_filter = NostrFilter(kinds: home_filter_kinds)
- // include our pubkey as well even if we're not technically a friend
- home_filter.authors = friends
- home_filter.limit = 500
-
var notifications_filter_kinds: [NostrKind] = [
.text,
.boost,
@@ -429,33 +476,71 @@ class HomeModel {
notifications_filter.pubkeys = [damus_state.pubkey]
notifications_filter.limit = 500
- var home_filters = [home_filter]
var notifications_filters = [notifications_filter]
var contacts_filters = [contacts_filter, our_contacts_filter, our_blocklist_filter]
var dms_filters = [dms_filter, our_dms_filter]
+ let last_of_kind = get_last_of_kind(relay_id: relay_id)
- let last_of_kind = relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
-
- home_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: home_filters)
contacts_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: contacts_filters)
notifications_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: notifications_filters)
dms_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: dms_filters)
//print_filters(relay_id: relay_id, filters: [home_filters, contacts_filters, notifications_filters, dms_filters])
- if let relay_id {
- pool.send(.subscribe(.init(filters: home_filters, sub_id: home_subid)), to: [relay_id])
- pool.send(.subscribe(.init(filters: contacts_filters, sub_id: contacts_subid)), to: [relay_id])
- pool.send(.subscribe(.init(filters: notifications_filters, sub_id: notifications_subid)), to: [relay_id])
- pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)), to: [relay_id])
- } else {
- pool.send(.subscribe(.init(filters: home_filters, sub_id: home_subid)))
- pool.send(.subscribe(.init(filters: contacts_filters, sub_id: contacts_subid)))
- pool.send(.subscribe(.init(filters: notifications_filters, sub_id: notifications_subid)))
- pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)))
+ subscribe_to_home_filters(relay_id: relay_id)
+
+ let relay_ids = relay_id.map { [$0] }
+
+ pool.send(.subscribe(.init(filters: contacts_filters, sub_id: contacts_subid)), to: relay_ids)
+ pool.send(.subscribe(.init(filters: notifications_filters, sub_id: notifications_subid)), to: relay_ids)
+ pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)), to: relay_ids)
+ }
+
+ func get_last_of_kind(relay_id: String?) -> [Int: NostrEvent] {
+ return relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
+ }
+
+ func unsubscribe_to_home_filters() {
+ pool.send(.unsubscribe(home_subid))
+ }
+
+ func get_friends() -> [String] {
+ var friends = damus_state.contacts.get_friend_list()
+ friends.insert(damus_state.pubkey)
+ return Array(friends)
+ }
+
+ func subscribe_to_home_filters(friends fs: [String]? = nil, relay_id: String? = nil) {
+ // TODO: separate likes?
+ let home_filter_kinds: [NostrKind] = [
+ .text, .longform, .boost
+ ]
+ //if !damus_state.settings.onlyzaps_mode {
+ //home_filter_kinds.append(.like)
+ //}
+
+ let friends = fs ?? get_friends()
+ var home_filter = NostrFilter(kinds: home_filter_kinds)
+ // include our pubkey as well even if we're not technically a friend
+ home_filter.authors = friends
+ home_filter.limit = 500
+
+ var home_filters = [home_filter]
+
+ let followed_hashtags = Array(damus_state.contacts.get_followed_hashtags())
+ if followed_hashtags.count != 0 {
+ var hashtag_filter = NostrFilter.filter_hashtag(followed_hashtags)
+ hashtag_filter.limit = 100
+ home_filters.append(hashtag_filter)
}
+
+ let relay_ids = relay_id.map { [$0] }
+ home_filters = update_filters_with_since(last_of_kind: get_last_of_kind(relay_id: relay_id), filters: home_filters)
+ let sub = NostrSubscribe(filters: home_filters, sub_id: home_subid)
+
+ pool.send(.subscribe(sub), to: relay_ids)
}
-
+
func handle_list_event(_ ev: NostrEvent) {
// we only care about our lists
guard ev.pubkey == damus_state.pubkey else {
@@ -614,32 +699,34 @@ func add_contact_if_friend(contacts: Contacts, ev: NostrEvent) {
func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
let contacts = state.contacts
- var new_pks = Set<String>()
+ var new_refs = Set<ReferencedId>()
// our contacts
for tag in ev.tags {
- if tag.count >= 2 && tag[0] == "p" {
- new_pks.insert(tag[1])
- }
+ guard let ref = tag_to_refid(tag) else { continue }
+ new_refs.insert(ref)
}
- var old_pks = Set<String>()
+ var old_refs = Set<ReferencedId>()
// find removed contacts
if let old_ev = m_old_ev {
for tag in old_ev.tags {
- if tag.count >= 2 && tag[0] == "p" {
- old_pks.insert(tag[1])
- }
+ guard let ref = tag_to_refid(tag) else { continue }
+ old_refs.insert(ref)
}
}
- let diff = new_pks.symmetricDifference(old_pks)
- for pk in diff {
- if new_pks.contains(pk) {
- notify(.followed, pk)
- contacts.add_friend_pubkey(pk)
+ let diff = new_refs.symmetricDifference(old_refs)
+ for ref in diff {
+ if new_refs.contains(ref) {
+ notify(.followed, ref)
+ if ref.key == "p" {
+ contacts.add_friend_pubkey(ref.ref_id)
+ }
} else {
- notify(.unfollowed, pk)
- contacts.remove_friend(pk)
+ notify(.unfollowed, ref)
+ if ref.key == "p" {
+ contacts.remove_friend(ref.ref_id)
+ }
}
}
diff --git a/damus/Views/FollowButtonView.swift b/damus/Views/FollowButtonView.swift
@@ -32,16 +32,16 @@ struct FollowButtonView: View {
}
}
.onReceive(handle_notify(.followed)) { notif in
- let pk = notif.object as! String
- if pk != target.pubkey {
+ let pk = notif.object as! ReferencedId
+ if pk.key == "p", pk.ref_id != target.pubkey {
return
}
self.follow_state = .follows
}
.onReceive(handle_notify(.unfollowed)) { notif in
- let pk = notif.object as! String
- if pk != target.pubkey {
+ let pk = notif.object as! ReferencedId
+ if pk.key == "p", pk.ref_id != target.pubkey {
return
}
diff --git a/damus/Views/SearchView.swift b/damus/Views/SearchView.swift
@@ -11,32 +11,70 @@ struct SearchView: View {
let appstate: DamusState
@ObservedObject var search: SearchModel
@Environment(\.dismiss) var dismiss
-
+
+ let height: CGFloat = 250.0
+
var body: some View {
- TimelineView<AnyView>(events: search.events, loading: $search.loading, damus: appstate, show_friend_icon: true, filter: { _ in true })
- .navigationBarTitle(describe_search(search.search))
- .onReceive(handle_notify(.switched_timeline)) { obj in
- dismiss()
- }
- .onAppear() {
- search.subscribe()
- }
- .onDisappear() {
- search.unsubscribe()
- }
- .onReceive(handle_notify(.new_mutes)) { notif in
- search.filter_muted()
+ TimelineView(events: search.events, loading: $search.loading, damus: appstate, show_friend_icon: true, filter: { _ in true }) {
+ ZStack(alignment: .leading) {
+ DamusBackground(maxHeight: height)
+ .mask(LinearGradient(gradient: Gradient(colors: [.black, .black, .black, .clear]), startPoint: .top, endPoint: .bottom))
+ SearchHeaderView(state: appstate, described: described_search)
+ .padding(.leading, 30)
+ .padding(.top, 100)
}
+ }
+ .ignoresSafeArea()
+ .onReceive(handle_notify(.switched_timeline)) { obj in
+ dismiss()
+ }
+ .onAppear() {
+ search.subscribe()
+ }
+ .onDisappear() {
+ search.unsubscribe()
+ }
+ .onReceive(handle_notify(.new_mutes)) { notif in
+ search.filter_muted()
+ }
+ }
+
+ var described_search: DescribedSearch {
+ return describe_search(search.search)
+ }
+}
+
+enum DescribedSearch {
+ case hashtag(String)
+ case unknown
+
+ var is_hashtag: String? {
+ switch self {
+ case .hashtag(let ht):
+ return ht
+ case .unknown:
+ return nil
+ }
+ }
+
+ var description: String {
+ switch self {
+ case .hashtag(let s):
+ return "#" + s
+ case .unknown:
+ return "Search"
+ }
}
}
-func describe_search(_ filter: NostrFilter) -> String {
+func describe_search(_ filter: NostrFilter) -> DescribedSearch {
if let hashtags = filter.hashtag {
if hashtags.count >= 1 {
- return "#" + hashtags[0]
+ return .hashtag(hashtags[0])
}
}
- return "Search"
+
+ return .unknown
}
struct SearchView_Previews: PreviewProvider {