home: hide users and hashtags from home timeline when you unfollow

Add the ability to resubscribe to home filters so that it will be
updated when you follow and unfollow people

Changelog-Fixed: Hide users and hashtags from home timeline when you unfollow
This commit is contained in:
William Casarin
2023-07-13 11:06:07 -07:00
parent 122655bea3
commit 31fa63debf
2 changed files with 102 additions and 22 deletions

View File

@@ -396,11 +396,21 @@ struct ContentView: View {
} }
.onReceive(handle_notify(.unfollow)) { notif in .onReceive(handle_notify(.unfollow)) { notif in
guard let state = self.damus_state else { return } guard let state = self.damus_state else { return }
handle_unfollow_notif(state: state, notif: notif) 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 .onReceive(handle_notify(.follow)) { notif in
guard let state = self.damus_state else { return } guard let state = self.damus_state else { return }
handle_follow_notif(state: state, notif: notif) 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 .onReceive(handle_notify(.post)) { notif in
guard let state = self.damus_state, guard let state = self.damus_state,
@@ -875,16 +885,17 @@ func timeline_name(_ timeline: Timeline?) -> String {
} }
} }
func handle_unfollow(state: DamusState, unfollow: ReferencedId) { @discardableResult
func handle_unfollow(state: DamusState, unfollow: ReferencedId) -> Bool {
guard let keypair = state.keypair.to_full() else { guard let keypair = state.keypair.to_full() else {
return return false
} }
let old_contacts = state.contacts.event let old_contacts = state.contacts.event
guard let ev = unfollow_reference(postbox: state.postbox, our_contacts: old_contacts, keypair: keypair, unfollow: unfollow) guard let ev = unfollow_reference(postbox: state.postbox, our_contacts: old_contacts, keypair: keypair, unfollow: unfollow)
else { else {
return return false
} }
notify(.unfollowed, unfollow) notify(.unfollowed, unfollow)
@@ -895,13 +906,20 @@ func handle_unfollow(state: DamusState, unfollow: ReferencedId) {
state.contacts.remove_friend(unfollow.ref_id) state.contacts.remove_friend(unfollow.ref_id)
state.user_search_cache.updateOwnContactsPetnames(id: state.pubkey, oldEvent: old_contacts, newEvent: ev) state.user_search_cache.updateOwnContactsPetnames(id: state.pubkey, oldEvent: old_contacts, newEvent: ev)
} }
return true
} }
func handle_unfollow_notif(state: DamusState, notif: Notification) { func handle_unfollow_notif(state: DamusState, notif: Notification) -> ReferencedId? {
let target = notif.object as! FollowTarget let target = notif.object as! FollowTarget
let pk = target.pubkey let pk = target.pubkey
handle_unfollow(state: state, unfollow: .p(pk)) let ref = ReferencedId.p(pk)
if handle_unfollow(state: state, unfollow: ref) {
return ref
}
return nil
} }
@discardableResult @discardableResult

View File

@@ -23,6 +23,40 @@ struct NewEventsBits: OptionSet {
static let notifications: NewEventsBits = [.zaps, .likes, .reposts, .mentions] 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 { class HomeModel {
// Don't trigger a user notification for events older than a certain age // Don't trigger a user notification for events older than a certain age
static let event_max_age_for_notification: TimeInterval = 12 * 60 * 60 static let event_max_age_for_notification: TimeInterval = 12 * 60 * 60
@@ -36,6 +70,7 @@ class HomeModel {
var done_init: Bool = false var done_init: Bool = false
var incoming_dms: [NostrEvent] = [] var incoming_dms: [NostrEvent] = []
let dm_debouncer = Debouncer(interval: 0.5) let dm_debouncer = Debouncer(interval: 0.5)
let resub_debouncer = Debouncer(interval: 3.0)
var should_debounce_dms = true var should_debounce_dms = true
let home_subid = UUID().description 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) { func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) { if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
return return
@@ -639,32 +699,34 @@ func add_contact_if_friend(contacts: Contacts, ev: NostrEvent) {
func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) { func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
let contacts = state.contacts let contacts = state.contacts
var new_pks = Set<String>() var new_refs = Set<ReferencedId>()
// our contacts // our contacts
for tag in ev.tags { for tag in ev.tags {
if tag.count >= 2 && tag[0] == "p" { guard let ref = tag_to_refid(tag) else { continue }
new_pks.insert(tag[1]) new_refs.insert(ref)
}
} }
var old_pks = Set<String>() var old_refs = Set<ReferencedId>()
// find removed contacts // find removed contacts
if let old_ev = m_old_ev { if let old_ev = m_old_ev {
for tag in old_ev.tags { for tag in old_ev.tags {
if tag.count >= 2 && tag[0] == "p" { guard let ref = tag_to_refid(tag) else { continue }
old_pks.insert(tag[1]) old_refs.insert(ref)
}
} }
} }
let diff = new_pks.symmetricDifference(old_pks) let diff = new_refs.symmetricDifference(old_refs)
for pk in diff { for ref in diff {
if new_pks.contains(pk) { if new_refs.contains(ref) {
notify(.followed, pk) notify(.followed, ref)
contacts.add_friend_pubkey(pk) if ref.key == "p" {
contacts.add_friend_pubkey(ref.ref_id)
}
} else { } else {
notify(.unfollowed, pk) notify(.unfollowed, ref)
contacts.remove_friend(pk) if ref.key == "p" {
contacts.remove_friend(ref.ref_id)
}
} }
} }