Add muting and mutelists
- Filter muted posts from feed on mute - List muted users in sidebar Changelog-Added: Added ability to block users
This commit is contained in:
@@ -159,6 +159,9 @@
|
|||||||
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD529817F5B00D66079 /* ReportView.swift */; };
|
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD529817F5B00D66079 /* ReportView.swift */; };
|
||||||
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD72981980C00D66079 /* Lists.swift */; };
|
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD72981980C00D66079 /* Lists.swift */; };
|
||||||
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABDB2981A19E00D66079 /* ListTests.swift */; };
|
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABDB2981A19E00D66079 /* ListTests.swift */; };
|
||||||
|
4CF0ABDE2981A69500D66079 /* MutelistModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABDD2981A69500D66079 /* MutelistModel.swift */; };
|
||||||
|
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE02981A83900D66079 /* MutelistView.swift */; };
|
||||||
|
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE22981BC7D00D66079 /* UserView.swift */; };
|
||||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||||
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
|
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
|
||||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
||||||
@@ -395,6 +398,9 @@
|
|||||||
4CF0ABD529817F5B00D66079 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; };
|
4CF0ABD529817F5B00D66079 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; };
|
||||||
4CF0ABD72981980C00D66079 /* Lists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lists.swift; sourceTree = "<group>"; };
|
4CF0ABD72981980C00D66079 /* Lists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lists.swift; sourceTree = "<group>"; };
|
||||||
4CF0ABDB2981A19E00D66079 /* ListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTests.swift; sourceTree = "<group>"; };
|
4CF0ABDB2981A19E00D66079 /* ListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTests.swift; sourceTree = "<group>"; };
|
||||||
|
4CF0ABDD2981A69500D66079 /* MutelistModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutelistModel.swift; sourceTree = "<group>"; };
|
||||||
|
4CF0ABE02981A83900D66079 /* MutelistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutelistView.swift; sourceTree = "<group>"; };
|
||||||
|
4CF0ABE22981BC7D00D66079 /* UserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserView.swift; sourceTree = "<group>"; };
|
||||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||||
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
|
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
|
||||||
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
||||||
@@ -538,6 +544,7 @@
|
|||||||
4CB88392296F798300DC99E7 /* ReactionsModel.swift */,
|
4CB88392296F798300DC99E7 /* ReactionsModel.swift */,
|
||||||
7C45AE70297353390031D7BC /* KFImageModel.swift */,
|
7C45AE70297353390031D7BC /* KFImageModel.swift */,
|
||||||
4CF0ABD32980996B00D66079 /* Report.swift */,
|
4CF0ABD32980996B00D66079 /* Report.swift */,
|
||||||
|
4CF0ABDD2981A69500D66079 /* MutelistModel.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -545,6 +552,7 @@
|
|||||||
4C75EFA227FA576C0006080F /* Views */ = {
|
4C75EFA227FA576C0006080F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4CF0ABDF2981A83000D66079 /* Muting */,
|
||||||
4CC7AAEE297F11B300430951 /* Events */,
|
4CC7AAEE297F11B300430951 /* Events */,
|
||||||
4CB88394296F7F8100DC99E7 /* Reactions */,
|
4CB88394296F7F8100DC99E7 /* Reactions */,
|
||||||
4CB88387296AF97C00DC99E7 /* ActionBar */,
|
4CB88387296AF97C00DC99E7 /* ActionBar */,
|
||||||
@@ -686,6 +694,7 @@
|
|||||||
4CB8838C296F710400DC99E7 /* Reposted.swift */,
|
4CB8838C296F710400DC99E7 /* Reposted.swift */,
|
||||||
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */,
|
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */,
|
||||||
4CC7AAEC297F0B9E00430951 /* Highlight.swift */,
|
4CC7AAEC297F0B9E00430951 /* Highlight.swift */,
|
||||||
|
4CF0ABE22981BC7D00D66079 /* UserView.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -775,6 +784,14 @@
|
|||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4CF0ABDF2981A83000D66079 /* Muting */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4CF0ABE02981A83900D66079 /* MutelistView.swift */,
|
||||||
|
);
|
||||||
|
path = Muting;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
F7F0BA23297892AE009531F3 /* Modifiers */ = {
|
F7F0BA23297892AE009531F3 /* Modifiers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -969,6 +986,7 @@
|
|||||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
||||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
||||||
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
||||||
|
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
||||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||||
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
|
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
|
||||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
||||||
@@ -988,6 +1006,7 @@
|
|||||||
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
||||||
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
|
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
|
||||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||||
|
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
|
||||||
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
||||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
||||||
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */,
|
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */,
|
||||||
@@ -1060,6 +1079,7 @@
|
|||||||
4C0A3F95280F6C78000448DE /* ReplyQuoteView.swift in Sources */,
|
4C0A3F95280F6C78000448DE /* ReplyQuoteView.swift in Sources */,
|
||||||
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
||||||
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
|
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
|
||||||
|
4CF0ABDE2981A69500D66079 /* MutelistModel.swift in Sources */,
|
||||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||||
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
||||||
|
|||||||
42
damus/Components/UserView.swift
Normal file
42
damus/Components/UserView.swift
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// UserView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-01-25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct UserView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
let pubkey: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
||||||
|
let followers = FollowersModel(damus_state: damus_state, target: pubkey)
|
||||||
|
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: followers)
|
||||||
|
|
||||||
|
NavigationLink(destination: pv) {
|
||||||
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||||
|
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false, show_nip5_domain: false)
|
||||||
|
if let about = profile?.about {
|
||||||
|
Text(about)
|
||||||
|
.lineLimit(3)
|
||||||
|
.font(.footnote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UserView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
UserView(damus_state: test_damus_state(), pubkey: "pk")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,6 +81,10 @@ struct ContentView: View {
|
|||||||
@State var profile_open: Bool = false
|
@State var profile_open: Bool = false
|
||||||
@State var thread_open: Bool = false
|
@State var thread_open: Bool = false
|
||||||
@State var search_open: Bool = false
|
@State var search_open: Bool = false
|
||||||
|
@State var blocking: String? = nil
|
||||||
|
@State var confirm_block: Bool = false
|
||||||
|
@State var user_blocked_confirm: Bool = false
|
||||||
|
@State var confirm_overwrite_mutelist: Bool = false
|
||||||
@State var filter_state : FilterState = .posts_and_replies
|
@State var filter_state : FilterState = .posts_and_replies
|
||||||
@State private var isSideBarOpened = false
|
@State private var isSideBarOpened = false
|
||||||
@StateObject var home: HomeModel = HomeModel()
|
@StateObject var home: HomeModel = HomeModel()
|
||||||
@@ -348,6 +352,11 @@ struct ContentView: View {
|
|||||||
let target = notif.object as! ReportTarget
|
let target = notif.object as! ReportTarget
|
||||||
self.active_sheet = .report(target)
|
self.active_sheet = .report(target)
|
||||||
}
|
}
|
||||||
|
.onReceive(handle_notify(.block)) { notif in
|
||||||
|
let pubkey = notif.object as! String
|
||||||
|
self.blocking = pubkey
|
||||||
|
self.confirm_block = true
|
||||||
|
}
|
||||||
.onReceive(handle_notify(.broadcast_event)) { obj in
|
.onReceive(handle_notify(.broadcast_event)) { obj in
|
||||||
let ev = obj.object as! NostrEvent
|
let ev = obj.object as! NostrEvent
|
||||||
self.damus_state?.pool.send(.event(ev))
|
self.damus_state?.pool.send(.event(ev))
|
||||||
@@ -422,6 +431,91 @@ struct ContentView: View {
|
|||||||
.onReceive(timer) { n in
|
.onReceive(timer) { n in
|
||||||
self.damus_state?.pool.connect_to_disconnected()
|
self.damus_state?.pool.connect_to_disconnected()
|
||||||
}
|
}
|
||||||
|
.onReceive(handle_notify(.new_mutes)) { notif in
|
||||||
|
home.filter_muted()
|
||||||
|
}
|
||||||
|
.alert("User blocked", isPresented: $user_blocked_confirm, actions: {
|
||||||
|
Button("Thanks!") {
|
||||||
|
user_blocked_confirm = false
|
||||||
|
}
|
||||||
|
}, message: {
|
||||||
|
if let pubkey = self.blocking {
|
||||||
|
let profile = damus_state!.profiles.lookup(id: pubkey)
|
||||||
|
let name = Profile.displayName(profile: profile, pubkey: pubkey)
|
||||||
|
Text("\(name) has been blocked")
|
||||||
|
} else {
|
||||||
|
Text("User has been blocked")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.alert("Create new mutelist", isPresented: $confirm_overwrite_mutelist, actions: {
|
||||||
|
Button("Yes, Overwrite") {
|
||||||
|
guard let ds = damus_state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let keypair = ds.keypair.to_full() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let pubkey = blocking else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: pubkey) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
damus_state?.contacts.set_mutelist(mutelist)
|
||||||
|
ds.pool.send(.event(mutelist))
|
||||||
|
|
||||||
|
confirm_overwrite_mutelist = false
|
||||||
|
confirm_block = false
|
||||||
|
user_blocked_confirm = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Cancel") {
|
||||||
|
confirm_overwrite_mutelist = false
|
||||||
|
confirm_block = false
|
||||||
|
}
|
||||||
|
}, message: {
|
||||||
|
Text("No block list found, create a new one? This will overwrite any previous block lists.")
|
||||||
|
})
|
||||||
|
.alert("Block User", isPresented: $confirm_block, actions: {
|
||||||
|
Button("Block") {
|
||||||
|
guard let ds = damus_state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds.contacts.mutelist == nil {
|
||||||
|
confirm_overwrite_mutelist = true
|
||||||
|
} else {
|
||||||
|
guard let keypair = ds.keypair.to_full() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let pubkey = blocking else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let ev = create_or_update_mutelist(keypair: keypair, mprev: ds.contacts.mutelist, to_add: pubkey) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
damus_state?.contacts.set_mutelist(ev)
|
||||||
|
ds.pool.send(.event(ev))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Cancel") {
|
||||||
|
confirm_block = false
|
||||||
|
}
|
||||||
|
}, message: {
|
||||||
|
if let pubkey = blocking {
|
||||||
|
let profile = damus_state?.profiles.lookup(id: pubkey)
|
||||||
|
let name = Profile.displayName(profile: profile, pubkey: pubkey)
|
||||||
|
Text("Block \(name)?")
|
||||||
|
} else {
|
||||||
|
Text("Could not find user to block...")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func switch_timeline(_ timeline: Timeline) {
|
func switch_timeline(_ timeline: Timeline) {
|
||||||
|
|||||||
@@ -11,13 +11,51 @@ import Foundation
|
|||||||
class Contacts {
|
class Contacts {
|
||||||
private var friends: Set<String> = Set()
|
private var friends: Set<String> = Set()
|
||||||
private var friend_of_friends: Set<String> = Set()
|
private var friend_of_friends: Set<String> = Set()
|
||||||
|
private var muted: Set<String> = Set()
|
||||||
|
|
||||||
let our_pubkey: String
|
let our_pubkey: String
|
||||||
var event: NostrEvent?
|
var event: NostrEvent?
|
||||||
|
var mutelist: NostrEvent?
|
||||||
|
|
||||||
init(our_pubkey: String) {
|
init(our_pubkey: String) {
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func is_muted(_ pk: String) -> Bool {
|
||||||
|
return muted.contains(pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func set_mutelist(_ ev: NostrEvent) {
|
||||||
|
let oldlist = self.mutelist
|
||||||
|
self.mutelist = ev
|
||||||
|
|
||||||
|
let old = Set(oldlist?.referenced_pubkeys.map({ $0.ref_id }) ?? [])
|
||||||
|
let new = Set(ev.referenced_pubkeys.map({ $0.ref_id }))
|
||||||
|
let diff = old.symmetricDifference(new)
|
||||||
|
|
||||||
|
var new_mutes = Array<String>()
|
||||||
|
var new_unmutes = Array<String>()
|
||||||
|
|
||||||
|
for d in diff {
|
||||||
|
if new.contains(d) {
|
||||||
|
new_mutes.append(d)
|
||||||
|
} else {
|
||||||
|
new_unmutes.append(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: set local mutelist here
|
||||||
|
self.muted = Set(ev.referenced_pubkeys.map({ $0.ref_id }))
|
||||||
|
|
||||||
|
if new_mutes.count > 0 {
|
||||||
|
notify(.new_mutes, new_mutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_unmutes.count > 0 {
|
||||||
|
notify(.new_unmutes, new_unmutes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func get_friendosphere() -> [String] {
|
func get_friendosphere() -> [String] {
|
||||||
var fs = get_friend_list()
|
var fs = get_friend_list()
|
||||||
fs.append(contentsOf: get_friend_of_friend_list())
|
fs.append(contentsOf: get_friend_of_friend_list())
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ class HomeModel: ObservableObject {
|
|||||||
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
||||||
case .metadata:
|
case .metadata:
|
||||||
handle_metadata_event(ev)
|
handle_metadata_event(ev)
|
||||||
|
case .list:
|
||||||
|
handle_list_event(ev)
|
||||||
case .boost:
|
case .boost:
|
||||||
handle_boost_event(sub_id: sub_id, ev)
|
handle_boost_event(sub_id: sub_id, ev)
|
||||||
case .like:
|
case .like:
|
||||||
@@ -124,6 +126,12 @@ class HomeModel: ObservableObject {
|
|||||||
func handle_channel_meta(_ ev: NostrEvent) {
|
func handle_channel_meta(_ ev: NostrEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filter_muted() {
|
||||||
|
self.events = events.filter { !damus_state.contacts.is_muted($0.pubkey) }
|
||||||
|
self.dms.dms = dms.dms.filter { !damus_state.contacts.is_muted($0.0) }
|
||||||
|
self.notifications = notifications.filter { !damus_state.contacts.is_muted($0.pubkey) }
|
||||||
|
}
|
||||||
|
|
||||||
func handle_delete_event(_ ev: NostrEvent) {
|
func handle_delete_event(_ ev: NostrEvent) {
|
||||||
guard ev.is_valid else {
|
guard ev.is_valid else {
|
||||||
return
|
return
|
||||||
@@ -274,7 +282,11 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
var our_contacts_filter = NostrFilter.filter_kinds([3, 0])
|
var our_contacts_filter = NostrFilter.filter_kinds([3, 0])
|
||||||
our_contacts_filter.authors = [damus_state.pubkey]
|
our_contacts_filter.authors = [damus_state.pubkey]
|
||||||
|
|
||||||
|
var our_blocklist_filter = NostrFilter.filter_kinds([30000])
|
||||||
|
our_blocklist_filter.parameter = "mute"
|
||||||
|
our_blocklist_filter.authors = [damus_state.pubkey]
|
||||||
|
|
||||||
var dms_filter = NostrFilter.filter_kinds([
|
var dms_filter = NostrFilter.filter_kinds([
|
||||||
NostrKind.dm.rawValue,
|
NostrKind.dm.rawValue,
|
||||||
])
|
])
|
||||||
@@ -311,7 +323,7 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
var home_filters = [home_filter]
|
var home_filters = [home_filter]
|
||||||
var notifications_filters = [notifications_filter]
|
var notifications_filters = [notifications_filter]
|
||||||
var contacts_filters = [contacts_filter, our_contacts_filter]
|
var contacts_filters = [contacts_filter, our_contacts_filter, our_blocklist_filter]
|
||||||
var dms_filters = [dms_filter, our_dms_filter]
|
var dms_filters = [dms_filter, our_dms_filter]
|
||||||
|
|
||||||
let last_of_kind = relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
|
let last_of_kind = relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
|
||||||
@@ -335,7 +347,30 @@ class HomeModel: ObservableObject {
|
|||||||
pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)))
|
pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handle_list_event(_ ev: NostrEvent) {
|
||||||
|
// we only care about our lists
|
||||||
|
guard ev.pubkey == damus_state.pubkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let mutelist = damus_state.contacts.mutelist {
|
||||||
|
if ev.created_at <= mutelist.created_at {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let name = get_referenced_ids(tags: ev.tags, key: "d").first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard name.ref_id == "mute" else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
damus_state.contacts.set_mutelist(ev)
|
||||||
|
}
|
||||||
|
|
||||||
func handle_metadata_event(_ ev: NostrEvent) {
|
func handle_metadata_event(_ ev: NostrEvent) {
|
||||||
process_metadata_event(profiles: damus_state.profiles, ev: ev)
|
process_metadata_event(profiles: damus_state.profiles, ev: ev)
|
||||||
}
|
}
|
||||||
@@ -376,6 +411,9 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func should_hide_event(_ ev: NostrEvent) -> Bool {
|
func should_hide_event(_ ev: NostrEvent) -> Bool {
|
||||||
|
if damus_state.contacts.is_muted(ev.pubkey) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return !ev.should_show_event
|
return !ev.should_show_event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
damus/Models/MutelistModel.swift
Normal file
18
damus/Models/MutelistModel.swift
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// ListModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-01-25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
class MutelistModel: ObservableObject {
|
||||||
|
let contacts: Contacts
|
||||||
|
|
||||||
|
@Published var users: [String]
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -17,6 +17,7 @@ struct NostrFilter: Codable {
|
|||||||
var limit: UInt32?
|
var limit: UInt32?
|
||||||
var authors: [String]?
|
var authors: [String]?
|
||||||
var hashtag: [String]? = nil
|
var hashtag: [String]? = nil
|
||||||
|
var parameter: String? = nil
|
||||||
|
|
||||||
private enum CodingKeys : String, CodingKey {
|
private enum CodingKeys : String, CodingKey {
|
||||||
case ids
|
case ids
|
||||||
@@ -24,6 +25,7 @@ struct NostrFilter: Codable {
|
|||||||
case referenced_ids = "#e"
|
case referenced_ids = "#e"
|
||||||
case pubkeys = "#p"
|
case pubkeys = "#p"
|
||||||
case hashtag = "#t"
|
case hashtag = "#t"
|
||||||
|
case parameter = "#d"
|
||||||
case since
|
case since
|
||||||
case until
|
case until
|
||||||
case authors
|
case authors
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ enum NostrKind: Int {
|
|||||||
case channel_create = 40
|
case channel_create = 40
|
||||||
case channel_meta = 41
|
case channel_meta = 41
|
||||||
case chat = 42
|
case chat = 42
|
||||||
|
case list = 30000
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,15 @@ extension Notification.Name {
|
|||||||
static var report: Notification.Name {
|
static var report: Notification.Name {
|
||||||
return Notification.Name("report")
|
return Notification.Name("report")
|
||||||
}
|
}
|
||||||
|
static var block: Notification.Name {
|
||||||
|
return Notification.Name("block")
|
||||||
|
}
|
||||||
|
static var new_mutes: Notification.Name {
|
||||||
|
return Notification.Name("new_mutes")
|
||||||
|
}
|
||||||
|
static var new_unmutes: Notification.Name {
|
||||||
|
return Notification.Name("new_unmutes")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {
|
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ struct EventMenuContext: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label(NSLocalizedString("Report", comment: "Context menu option for reporting content."), systemImage: "exclamationmark.bubble")
|
Label(NSLocalizedString("Report", comment: "Context menu option for reporting content."), systemImage: "exclamationmark.bubble")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
notify(.block, event.pubkey)
|
||||||
|
} label: {
|
||||||
|
Label(NSLocalizedString("Block", comment: "Context menu option for blocking users."), systemImage: "exclamationmark.octagon")
|
||||||
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
NotificationCenter.default.post(name: .broadcast_event, object: event)
|
NotificationCenter.default.post(name: .broadcast_event, object: event)
|
||||||
|
|||||||
@@ -15,26 +15,7 @@ struct FollowUserView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
let pmodel = ProfileModel(pubkey: target.pubkey, damus: damus_state)
|
UserView(damus_state: damus_state, pubkey: target.pubkey)
|
||||||
let followers = FollowersModel(damus_state: damus_state, target: target.pubkey)
|
|
||||||
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: followers)
|
|
||||||
|
|
||||||
NavigationLink(destination: pv) {
|
|
||||||
ProfilePicView(pubkey: target.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
let profile = damus_state.profiles.lookup(id: target.pubkey)
|
|
||||||
ProfileName(pubkey: target.pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false, show_nip5_domain: false)
|
|
||||||
if let about = profile?.about {
|
|
||||||
Text(FollowUserView.markdown.process(about))
|
|
||||||
.lineLimit(3)
|
|
||||||
.font(.footnote)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
|
|
||||||
FollowButtonView(target: target, follow_state: damus_state.contacts.follow_state(target.pubkey))
|
FollowButtonView(target: target, follow_state: damus_state.contacts.follow_state(target.pubkey))
|
||||||
}
|
}
|
||||||
|
|||||||
67
damus/Views/Muting/MutelistView.swift
Normal file
67
damus/Views/Muting/MutelistView.swift
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// MutelistView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-01-25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MutelistView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
@State var users: [String]
|
||||||
|
|
||||||
|
func RemoveAction(pubkey: String) -> some View {
|
||||||
|
Button {
|
||||||
|
guard let mutelist = damus_state.contacts.mutelist else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let keypair = damus_state.keypair.to_full() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: pubkey) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
damus_state.contacts.set_mutelist(new_ev)
|
||||||
|
damus_state.pool.send(.event(new_ev))
|
||||||
|
users = get_mutelist_users(new_ev)
|
||||||
|
} label: {
|
||||||
|
Label(NSLocalizedString("Delete", comment: "Button to remove a user from their blocklist."), systemImage: "trash")
|
||||||
|
}
|
||||||
|
.tint(.red)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List(users, id: \.self) { pubkey in
|
||||||
|
UserView(damus_state: damus_state, pubkey: pubkey)
|
||||||
|
.id(pubkey)
|
||||||
|
.swipeActions {
|
||||||
|
RemoveAction(pubkey: pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Blocked Users")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func get_mutelist_users(_ mlist: NostrEvent?) -> [String] {
|
||||||
|
guard let mutelist = mlist else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutelist.tags.reduce(into: Array<String>()) { pks, tag in
|
||||||
|
if tag.count >= 2 && tag[0] == "p" {
|
||||||
|
pks.append(tag[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MutelistView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
MutelistView(damus_state: test_damus_state(), users: [test_event.pubkey, test_event.pubkey+"hi"])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -383,6 +383,10 @@ struct ProfileView: View {
|
|||||||
let target: ReportTarget = .user(profile.pubkey)
|
let target: ReportTarget = .user(profile.pubkey)
|
||||||
notify(.report, target)
|
notify(.report, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button("Block") {
|
||||||
|
notify(.block, profile.pubkey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,22 +75,6 @@ struct SideMenuView: View {
|
|||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
.padding(.trailing,40)
|
.padding(.trailing,40)
|
||||||
|
|
||||||
/*
|
|
||||||
HStack(alignment: .bottom) {
|
|
||||||
Text("69,420")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.font(.largeTitle)
|
|
||||||
Text("SATS")
|
|
||||||
.font(.caption)
|
|
||||||
.padding(.bottom,6)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
.padding(.trailing,40)
|
|
||||||
*/
|
|
||||||
|
|
||||||
// THERE IS A LIMIT OF 10 NAVIGATIONLINKS!!! (Consider some in other views)
|
|
||||||
|
|
||||||
NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers)) {
|
NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers)) {
|
||||||
Label(NSLocalizedString("Profile", comment: "Sidebar menu label for Profile view."), systemImage: "person")
|
Label(NSLocalizedString("Profile", comment: "Sidebar menu label for Profile view."), systemImage: "person")
|
||||||
@@ -123,6 +107,12 @@ struct SideMenuView: View {
|
|||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
NavigationLink(destination: MutelistView(damus_state: damus_state, users: get_mutelist_users(damus_state.contacts.mutelist) )) {
|
||||||
|
Label(NSLocalizedString("Blocked", comment: "Sidebar menu label for Profile view."), systemImage: "exclamationmark.octagon")
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundColor(textColor())
|
||||||
|
}
|
||||||
|
|
||||||
NavigationLink(destination: ConfigView(state: damus_state).environmentObject(user_settings)) {
|
NavigationLink(destination: ConfigView(state: damus_state).environmentObject(user_settings)) {
|
||||||
Label(NSLocalizedString("Settings", comment: "Sidebar menu label for accessing the app settings"), systemImage: "gear")
|
Label(NSLocalizedString("Settings", comment: "Sidebar menu label for accessing the app settings"), systemImage: "gear")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
|
|||||||
Reference in New Issue
Block a user