Add the ability to follow hashtags
Changelog-Added: Add the ability to follow hashtags
This commit is contained in:
@@ -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 */,
|
||||
|
||||
134
damus/Components/Search/SearchHeaderView.swift
Normal file
134
damus/Components/Search/SearchHeaderView.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
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
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func handle_follow(state: DamusState, follow: ReferencedId) -> Bool {
|
||||
guard let keypair = state.keypair.to_full() else {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
let fnotify = notif.object as! FollowTarget
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
func describe_search(_ filter: NostrFilter) -> String {
|
||||
if let hashtags = filter.hashtag {
|
||||
if hashtags.count >= 1 {
|
||||
return "#" + hashtags[0]
|
||||
enum DescribedSearch {
|
||||
case hashtag(String)
|
||||
case unknown
|
||||
|
||||
var is_hashtag: String? {
|
||||
switch self {
|
||||
case .hashtag(let ht):
|
||||
return ht
|
||||
case .unknown:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return "Search"
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .hashtag(let s):
|
||||
return "#" + s
|
||||
case .unknown:
|
||||
return "Search"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func describe_search(_ filter: NostrFilter) -> DescribedSearch {
|
||||
if let hashtags = filter.hashtag {
|
||||
if hashtags.count >= 1 {
|
||||
return .hashtag(hashtags[0])
|
||||
}
|
||||
}
|
||||
|
||||
return .unknown
|
||||
}
|
||||
|
||||
struct SearchView_Previews: PreviewProvider {
|
||||
|
||||
Reference in New Issue
Block a user