mirror of
git://jb55.com/damus
synced 2024-09-30 00:40:45 +00:00
Show zap comments in threads and show top zap
Changelog-Added: Top zaps Changelog-Added: Show zap comments in threads
This commit is contained in:
parent
8f237b47eb
commit
043eb5b436
@ -270,12 +270,12 @@
|
||||
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
|
||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||
50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; };
|
||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; };
|
||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
||||
5019CADD2A0FB0A9000069E1 /* ProfileDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */; };
|
||||
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */; };
|
||||
501F8C5829FF5FC5001AFC1D /* Damus.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */; };
|
||||
501F8C5A29FF70F5001AFC1D /* ProfileDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */; };
|
||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; };
|
||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
||||
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
|
||||
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
|
||||
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; };
|
||||
@ -714,12 +714,12 @@
|
||||
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
|
||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||
50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
|
||||
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
|
||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
||||
5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabaseTests.swift; sourceTree = "<group>"; };
|
||||
501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistedProfile.swift; sourceTree = "<group>"; };
|
||||
501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Damus.xcdatamodel; sourceTree = "<group>"; };
|
||||
501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabase.swift; sourceTree = "<group>"; };
|
||||
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
|
||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
||||
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
|
||||
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
|
||||
50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = "<group>"; };
|
||||
@ -2208,7 +2208,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -2256,7 +2256,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -35,7 +35,7 @@ struct ZapButton: View {
|
||||
@StateObject var button: ZapButtonModel = ZapButtonModel()
|
||||
|
||||
var our_zap: Zapping? {
|
||||
zaps.zaps.first(where: { z in z.request.pubkey == damus_state.pubkey })
|
||||
zaps.zaps.first(where: { z in z.request.ev.pubkey == damus_state.pubkey })
|
||||
}
|
||||
|
||||
var zap_img: String {
|
||||
|
@ -20,7 +20,7 @@ class ActionBarModel: ObservableObject {
|
||||
@Published var our_zap: Zapping?
|
||||
@Published var likes: Int
|
||||
@Published var boosts: Int
|
||||
@Published var zaps: Int
|
||||
@Published private(set) var zaps: Int
|
||||
@Published var zap_total: Int64
|
||||
@Published var replies: Int
|
||||
|
||||
|
@ -35,8 +35,16 @@ struct DamusState {
|
||||
func add_zap(zap: Zapping) -> Bool {
|
||||
// store generic zap mapping
|
||||
self.zaps.add_zap(zap: zap)
|
||||
let stored = self.events.store_zap(zap: zap)
|
||||
|
||||
// thread zaps
|
||||
if let ev = zap.event, zap.is_in_thread {
|
||||
replies.count_replies(ev)
|
||||
events.add_replies(ev: ev)
|
||||
}
|
||||
|
||||
// associate with events as well
|
||||
return self.events.store_zap(zap: zap)
|
||||
return stored
|
||||
}
|
||||
|
||||
var pubkey: String {
|
||||
|
@ -164,70 +164,38 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func handle_zap_event_with_zapper(profiles: Profiles, ev: NostrEvent, our_keypair: Keypair, zapper: String) {
|
||||
|
||||
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
|
||||
return
|
||||
}
|
||||
|
||||
damus_state.add_zap(zap: .zap(zap))
|
||||
|
||||
guard zap.target.pubkey == our_keypair.pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
if !notifications.insert_zap(.zap(zap)) {
|
||||
return
|
||||
}
|
||||
|
||||
if handle_last_event(ev: ev, timeline: .notifications) {
|
||||
if damus_state.settings.zap_vibration {
|
||||
// Generate zap vibration
|
||||
zap_vibrate(zap_amount: zap.invoice.amount)
|
||||
}
|
||||
if damus_state.settings.zap_notification {
|
||||
// Create in-app local notification for zap received.
|
||||
switch zap.target {
|
||||
case .profile(let profile_id):
|
||||
create_in_app_profile_zap_notification(profiles: profiles, zap: zap, profile_id: profile_id)
|
||||
case .note(let note_target):
|
||||
create_in_app_event_zap_notification(profiles: profiles, zap: zap, evId: note_target.note_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func handle_zap_event(_ ev: NostrEvent) {
|
||||
// These are zap notifications
|
||||
guard let ptag = event_tag(ev, name: "p") else {
|
||||
return
|
||||
}
|
||||
process_zap_event(damus_state: damus_state, ev: ev) { zapres in
|
||||
guard case .done(let zap) = zapres else { return }
|
||||
|
||||
guard zap.target.pubkey == self.damus_state.keypair.pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
let our_keypair = damus_state.keypair
|
||||
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
|
||||
handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: local_zapper)
|
||||
return
|
||||
}
|
||||
|
||||
guard let profile = damus_state.profiles.lookup(id: ptag) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let lnurl = profile.lnurl else {
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
guard let zapper = await fetch_zapper_from_lnurl(lnurl) else {
|
||||
if !self.notifications.insert_zap(.zap(zap)) {
|
||||
return
|
||||
}
|
||||
|
||||
guard let new_bits = handle_last_events(new_events: self.new_events, ev: ev, timeline: .notifications, shouldNotify: true) else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.damus_state.profiles.zappers[ptag] = zapper
|
||||
self.handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: zapper)
|
||||
if self.damus_state.settings.zap_vibration {
|
||||
// Generate zap vibration
|
||||
zap_vibrate(zap_amount: zap.invoice.amount)
|
||||
}
|
||||
|
||||
if self.damus_state.settings.zap_notification {
|
||||
// Create in-app local notification for zap received.
|
||||
switch zap.target {
|
||||
case .profile(let profile_id):
|
||||
create_in_app_profile_zap_notification(profiles: self.damus_state.profiles, zap: zap, profile_id: profile_id)
|
||||
case .note(let note_target):
|
||||
create_in_app_event_zap_notification(profiles: self.damus_state.profiles, zap: zap, evId: note_target.note_id)
|
||||
}
|
||||
}
|
||||
|
||||
self.new_events = new_bits
|
||||
}
|
||||
|
||||
}
|
||||
@ -1106,7 +1074,7 @@ func zap_notification_title(_ zap: Zap) -> String {
|
||||
}
|
||||
|
||||
func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String {
|
||||
let src = zap.private_request ?? zap.request.ev
|
||||
let src = zap.request.ev
|
||||
let anon = event_is_anonymous(ev: src)
|
||||
let pk = anon ? "anon" : src.pubkey
|
||||
let profile = profiles.lookup(id: pk)
|
||||
@ -1250,3 +1218,75 @@ func create_local_notification(profiles: Profiles, notify: LocalNotification) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum ProcessZapResult {
|
||||
case already_processed(Zap)
|
||||
case done(Zap)
|
||||
case failed
|
||||
}
|
||||
|
||||
func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) {
|
||||
// These are zap notifications
|
||||
guard let ptag = event_tag(ev, name: "p") else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
// just return the zap if we already have it
|
||||
if let zap = damus_state.zaps.zaps[ev.id], case .zap(let z) = zap {
|
||||
completion(.already_processed(z))
|
||||
return
|
||||
}
|
||||
|
||||
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
|
||||
guard let zap = process_zap_event_with_zapper(damus_state: damus_state, ev: ev, zapper: local_zapper) else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
damus_state.add_zap(zap: .zap(zap))
|
||||
completion(.done(zap))
|
||||
return
|
||||
}
|
||||
|
||||
guard let profile = damus_state.profiles.lookup(id: ptag) else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
guard let lnurl = profile.lnurl else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
guard let zapper = await fetch_zapper_from_lnurl(lnurl) else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
damus_state.profiles.zappers[ptag] = zapper
|
||||
guard let zap = process_zap_event_with_zapper(damus_state: damus_state, ev: ev, zapper: zapper) else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
damus_state.add_zap(zap: .zap(zap))
|
||||
completion(.done(zap))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fileprivate func process_zap_event_with_zapper(damus_state: DamusState, ev: NostrEvent, zapper: String) -> Zap? {
|
||||
let our_keypair = damus_state.keypair
|
||||
|
||||
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
damus_state.add_zap(zap: .zap(zap))
|
||||
|
||||
return zap
|
||||
}
|
||||
|
||||
|
@ -21,12 +21,12 @@ class ZapGroup {
|
||||
}
|
||||
|
||||
func zap_requests() -> [NostrEvent] {
|
||||
zaps.map { z in z.request }
|
||||
zaps.map { z in z.request.ev }
|
||||
}
|
||||
|
||||
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
|
||||
for zap in zaps {
|
||||
if !isIncluded(zap.request) {
|
||||
if !isIncluded(zap.request.ev) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -35,7 +35,7 @@ class ZapGroup {
|
||||
}
|
||||
|
||||
func filter(_ isIncluded: (NostrEvent) -> Bool) -> ZapGroup? {
|
||||
let new_zaps = zaps.filter { isIncluded($0.request) }
|
||||
let new_zaps = zaps.filter { isIncluded($0.request.ev) }
|
||||
guard new_zaps.count > 0 else {
|
||||
return nil
|
||||
}
|
||||
@ -60,8 +60,8 @@ class ZapGroup {
|
||||
|
||||
msat_total += zap.amount
|
||||
|
||||
if !zappers.contains(zap.request.pubkey) {
|
||||
zappers.insert(zap.request.pubkey)
|
||||
if !zappers.contains(zap.request.ev.pubkey) {
|
||||
zappers.insert(zap.request.ev.pubkey)
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -150,7 +150,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
}
|
||||
|
||||
for zap in incoming_zaps {
|
||||
pks.insert(zap.request.pubkey)
|
||||
pks.insert(zap.request.ev.pubkey)
|
||||
}
|
||||
|
||||
return Array(pks)
|
||||
@ -307,7 +307,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
changed = changed || incoming_events.count != count
|
||||
|
||||
count = profile_zaps.zaps.count
|
||||
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request) }
|
||||
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request.ev) }
|
||||
changed = changed || profile_zaps.zaps.count != count
|
||||
|
||||
for el in reactions {
|
||||
@ -325,7 +325,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
for el in zaps {
|
||||
count = el.value.zaps.count
|
||||
el.value.zaps = el.value.zaps.filter {
|
||||
isIncluded($0.request)
|
||||
isIncluded($0.request.ev)
|
||||
}
|
||||
changed = changed || el.value.zaps.count != count
|
||||
}
|
||||
|
@ -10,15 +10,21 @@ import Foundation
|
||||
/// manages the lifetime of a thread
|
||||
class ThreadModel: ObservableObject {
|
||||
@Published var event: NostrEvent
|
||||
let original_event: NostrEvent
|
||||
var event_map: Set<NostrEvent>
|
||||
|
||||
init(event: NostrEvent, damus_state: DamusState) {
|
||||
self.damus_state = damus_state
|
||||
self.event_map = Set()
|
||||
self.event = event
|
||||
self.original_event = event
|
||||
add_event(event)
|
||||
}
|
||||
|
||||
var is_original: Bool {
|
||||
return original_event.id == event.id
|
||||
}
|
||||
|
||||
let damus_state: DamusState
|
||||
|
||||
let profiles_subid = UUID().description
|
||||
@ -101,6 +107,10 @@ class ThreadModel: ObservableObject {
|
||||
|
||||
if ev.known_kind == .metadata {
|
||||
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||
} else if ev.known_kind == .zap {
|
||||
process_zap_event(damus_state: damus_state, ev: ev) { zap in
|
||||
|
||||
}
|
||||
} else if ev.is_textlike {
|
||||
self.add_event(ev)
|
||||
}
|
||||
@ -116,3 +126,10 @@ class ThreadModel: ObservableObject {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
func get_top_zap(events: EventCache, evid: String) -> Zapping? {
|
||||
return events.get_cache_data(evid).zaps_model.zaps.first(where: { zap in
|
||||
!zap.request.marked_hidden
|
||||
})
|
||||
}
|
||||
|
@ -53,18 +53,13 @@ class ZapsModel: ObservableObject {
|
||||
case .notice:
|
||||
break
|
||||
case .eose:
|
||||
let events = state.events.lookup_zaps(target: target).map { $0.request }
|
||||
let events = state.events.lookup_zaps(target: target).map { $0.request.ev }
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
||||
case .event(_, let ev):
|
||||
guard ev.kind == 9735 else {
|
||||
return
|
||||
}
|
||||
|
||||
if let zap = state.zaps.zaps[ev.id] {
|
||||
state.events.store_zap(zap: zap)
|
||||
return
|
||||
}
|
||||
|
||||
guard let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey) else {
|
||||
return
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class ZapsDataModel: ObservableObject {
|
||||
}
|
||||
|
||||
func confirm_nwc(reqid: String) {
|
||||
guard let zap = zaps.first(where: { z in z.request.id == reqid }),
|
||||
guard let zap = zaps.first(where: { z in z.request.ev.id == reqid }),
|
||||
case .pending(let pzap) = zap
|
||||
else {
|
||||
return
|
||||
@ -83,16 +83,16 @@ class ZapsDataModel: ObservableObject {
|
||||
}
|
||||
|
||||
func from(_ pubkey: String) -> [Zapping] {
|
||||
return self.zaps.filter { z in z.request.pubkey == pubkey }
|
||||
return self.zaps.filter { z in z.request.ev.pubkey == pubkey }
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func remove(reqid: String) -> Bool {
|
||||
guard zaps.first(where: { z in z.request.id == reqid }) != nil else {
|
||||
guard zaps.first(where: { z in z.request.ev.id == reqid }) != nil else {
|
||||
return false
|
||||
}
|
||||
|
||||
self.zaps = zaps.filter { z in z.request.id != reqid }
|
||||
self.zaps = zaps.filter { z in z.request.ev.id != reqid }
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -175,6 +175,9 @@ class EventCache {
|
||||
@discardableResult
|
||||
func store_zap(zap: Zapping) -> Bool {
|
||||
let data = get_cache_data(zap.target.id).zaps_model
|
||||
if let ev = zap.event {
|
||||
insert(ev)
|
||||
}
|
||||
return insert_uniq_sorted_zap_by_amount(zaps: &data.zaps, new_zap: zap)
|
||||
}
|
||||
|
||||
@ -182,7 +185,7 @@ class EventCache {
|
||||
switch zap.target {
|
||||
case .note(let note_target):
|
||||
let zaps = get_cache_data(note_target.note_id).zaps_model
|
||||
zaps.remove(reqid: zap.request.id)
|
||||
zaps.remove(reqid: zap.request.ev.id)
|
||||
case .profile:
|
||||
// these aren't stored anywhere yet
|
||||
break
|
||||
|
@ -11,10 +11,10 @@ func insert_uniq_sorted_zap(zaps: inout [Zapping], new_zap: Zapping, cmp: (Zappi
|
||||
var i: Int = 0
|
||||
|
||||
for zap in zaps {
|
||||
if new_zap.request.id == zap.request.id {
|
||||
if new_zap.request.ev.id == zap.request.ev.id {
|
||||
// replace pending
|
||||
if !new_zap.is_pending && zap.is_pending {
|
||||
print("nwc: replacing pending with real zap \(new_zap.request.id)")
|
||||
print("nwc: replacing pending with real zap \(new_zap.request.ev.id)")
|
||||
zaps[i] = new_zap
|
||||
return true
|
||||
}
|
||||
|
@ -41,7 +41,16 @@ public enum ZapTarget: Equatable {
|
||||
|
||||
struct ZapRequest {
|
||||
let ev: NostrEvent
|
||||
let marked_hidden: Bool
|
||||
|
||||
var is_in_thread: Bool {
|
||||
return !self.ev.content.isEmpty && !marked_hidden
|
||||
}
|
||||
|
||||
init(ev: NostrEvent) {
|
||||
self.ev = ev
|
||||
self.marked_hidden = ev.tags.first(where: { t in t.count > 0 && t[0] == "hidden" }) != nil
|
||||
}
|
||||
}
|
||||
|
||||
enum ExtPendingZapStateType {
|
||||
@ -129,7 +138,7 @@ struct ZapRequestId: Equatable {
|
||||
let reqid: String
|
||||
|
||||
init(from_zap: Zapping) {
|
||||
self.reqid = from_zap.request.id
|
||||
self.reqid = from_zap.request.ev.id
|
||||
}
|
||||
|
||||
init(from_makezap: MakeZapRequest) {
|
||||
@ -198,12 +207,12 @@ enum Zapping {
|
||||
}
|
||||
}
|
||||
|
||||
var request: NostrEvent {
|
||||
var request: ZapRequest {
|
||||
switch self {
|
||||
case .zap(let zap):
|
||||
return zap.request_ev
|
||||
return zap.request
|
||||
case .pending(let pzap):
|
||||
return pzap.request.ev
|
||||
return pzap.request
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,6 +236,15 @@ enum Zapping {
|
||||
}
|
||||
}
|
||||
|
||||
var is_in_thread: Bool {
|
||||
switch self {
|
||||
case .zap(let zap):
|
||||
return zap.request.is_in_thread
|
||||
case .pending(let pzap):
|
||||
return pzap.request.is_in_thread
|
||||
}
|
||||
}
|
||||
|
||||
var is_anon: Bool {
|
||||
switch self {
|
||||
case .zap(let zap):
|
||||
@ -242,12 +260,12 @@ struct Zap {
|
||||
public let invoice: ZapInvoice
|
||||
public let zapper: String /// zap authorizer
|
||||
public let target: ZapTarget
|
||||
public let request: ZapRequest
|
||||
public let raw_request: ZapRequest
|
||||
public let is_anon: Bool
|
||||
public let private_request: NostrEvent?
|
||||
public let private_request: ZapRequest?
|
||||
|
||||
var request_ev: NostrEvent {
|
||||
return private_request ?? self.request.ev
|
||||
var request: ZapRequest {
|
||||
return private_request ?? self.raw_request
|
||||
}
|
||||
|
||||
public static func from_zap_event(zap_ev: NostrEvent, zapper: String, our_privkey: String?) -> Zap? {
|
||||
@ -295,8 +313,9 @@ struct Zap {
|
||||
}
|
||||
|
||||
let is_anon = private_request == nil && event_is_anonymous(ev: zap_req)
|
||||
let preq = private_request.map { pr in ZapRequest(ev: pr) }
|
||||
|
||||
return Zap(event: zap_ev, invoice: zap_invoice, zapper: zapper, target: target, request: ZapRequest(ev: zap_req), is_anon: is_anon, private_request: private_request)
|
||||
return Zap(event: zap_ev, invoice: zap_invoice, zapper: zapper, target: target, raw_request: ZapRequest(ev: zap_req), is_anon: is_anon, private_request: preq)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,8 @@ class Zaps {
|
||||
let our_pubkey: String
|
||||
var our_zaps: [String: [Zapping]]
|
||||
|
||||
var event_counts: [String: Int]
|
||||
var event_totals: [String: Int64]
|
||||
private(set) var event_counts: [String: Int]
|
||||
private(set) var event_totals: [String: Int64]
|
||||
|
||||
init(our_pubkey: String) {
|
||||
self.zaps = [:]
|
||||
@ -27,13 +27,13 @@ class Zaps {
|
||||
var res: Zapping? = nil
|
||||
for kv in our_zaps {
|
||||
let ours = kv.value
|
||||
guard let zap = ours.first(where: { z in z.request.id == reqid }) else {
|
||||
guard let zap = ours.first(where: { z in z.request.ev.id == reqid }) else {
|
||||
continue
|
||||
}
|
||||
|
||||
res = zap
|
||||
|
||||
our_zaps[kv.key] = ours.filter { z in z.request.id != reqid }
|
||||
our_zaps[kv.key] = ours.filter { z in z.request.ev.id != reqid }
|
||||
|
||||
if let count = event_counts[zap.target.id] {
|
||||
event_counts[zap.target.id] = count - 1
|
||||
@ -51,13 +51,16 @@ class Zaps {
|
||||
}
|
||||
|
||||
func add_zap(zap: Zapping) {
|
||||
if zaps[zap.request.id] != nil {
|
||||
if zaps[zap.request.ev.id] != nil {
|
||||
return
|
||||
}
|
||||
self.zaps[zap.request.id] = zap
|
||||
self.zaps[zap.request.ev.id] = zap
|
||||
if let zap_id = zap.event?.id {
|
||||
self.zaps[zap_id] = zap
|
||||
}
|
||||
|
||||
// record our zaps for an event
|
||||
if zap.request.pubkey == our_pubkey {
|
||||
if zap.request.ev.pubkey == our_pubkey {
|
||||
switch zap.target {
|
||||
case .note(let note_target):
|
||||
if our_zaps[note_target.note_id] == nil {
|
||||
@ -71,7 +74,7 @@ class Zaps {
|
||||
}
|
||||
|
||||
// don't count tips to self. lame.
|
||||
guard zap.request.pubkey != zap.target.pubkey else {
|
||||
guard zap.request.ev.pubkey != zap.target.pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,9 @@ struct EventView: View {
|
||||
}
|
||||
} else if event.known_kind == .zap {
|
||||
if let zap = damus.zaps.zaps[event.id] {
|
||||
ZapEvent(damus: damus, zap: zap)
|
||||
ZapEvent(damus: damus, zap: zap, is_top_zap: options.contains(.top_zap))
|
||||
} else {
|
||||
Text("Invalid Zap")
|
||||
EmptyView()
|
||||
}
|
||||
} else {
|
||||
|
@ -18,6 +18,7 @@ struct EventViewOptions: OptionSet {
|
||||
static let no_translate = EventViewOptions(rawValue: 1 << 6)
|
||||
static let small_pfp = EventViewOptions(rawValue: 1 << 7)
|
||||
static let nested = EventViewOptions(rawValue: 1 << 8)
|
||||
static let top_zap = EventViewOptions(rawValue: 1 << 9)
|
||||
|
||||
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
|
||||
}
|
||||
|
@ -10,13 +10,23 @@ import SwiftUI
|
||||
struct ZapEvent: View {
|
||||
let damus: DamusState
|
||||
let zap: Zapping
|
||||
let is_top_zap: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
Text("⚡️ \(format_msats(zap.amount))", comment: "Text indicating the zap amount. i.e. number of satoshis that were tipped to a user")
|
||||
Image("zap.fill")
|
||||
.foregroundColor(.orange)
|
||||
|
||||
Text("\(format_msats(zap.amount))", comment: "Text indicating the zap amount. i.e. number of satoshis that were tipped to a user")
|
||||
.font(.headline)
|
||||
.padding([.top], 2)
|
||||
|
||||
if is_top_zap {
|
||||
Text("Top Zap")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
.padding([.top], 2)
|
||||
}
|
||||
|
||||
if zap.is_private {
|
||||
Image("lock")
|
||||
@ -31,7 +41,7 @@ struct ZapEvent: View {
|
||||
}
|
||||
}
|
||||
|
||||
TextEvent(damus: damus, event: zap.request, pubkey: zap.request.pubkey, options: [.no_action_bar, .no_replying_to])
|
||||
TextEvent(damus: damus, event: zap.request.ev, pubkey: zap.request.ev.pubkey, options: [.no_action_bar, .no_replying_to])
|
||||
.padding([.top], 1)
|
||||
}
|
||||
}
|
||||
@ -41,18 +51,18 @@ struct ZapEvent: View {
|
||||
let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, payment_hash: Data(), created_at: 1000000)
|
||||
let test_zap_request_ev = NostrEvent(content: "hi", pubkey: "pk", kind: 9734)
|
||||
let test_zap_request = ZapRequest(ev: test_zap_request_ev)
|
||||
let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), request: test_zap_request, is_anon: false, private_request: nil)
|
||||
let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: nil)
|
||||
|
||||
let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), request: test_zap_request, is_anon: false, private_request: test_event)
|
||||
let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: .init(ev: test_event))
|
||||
|
||||
let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: "id", author: "pk"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
|
||||
|
||||
struct ZapEvent_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_zap))
|
||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_zap), is_top_zap: true)
|
||||
|
||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_private_zap))
|
||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_private_zap), is_top_zap: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType
|
||||
return NSLocalizedString("Anonymous", comment: "Placeholder author name of the anonymous person who zapped an event.")
|
||||
}
|
||||
|
||||
return event_author_name(profiles: profiles, pubkey: zap.request.pubkey)
|
||||
return event_author_name(profiles: profiles, pubkey: zap.request.ev.pubkey)
|
||||
} else {
|
||||
let ev = group.events[ind]
|
||||
return event_author_name(profiles: profiles, pubkey: ev.pubkey)
|
||||
|
@ -10,9 +10,18 @@ import SwiftUI
|
||||
struct ThreadView: View {
|
||||
let state: DamusState
|
||||
|
||||
@StateObject var thread: ThreadModel
|
||||
@ObservedObject var thread: ThreadModel
|
||||
@ObservedObject var zaps: ZapsDataModel
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
init(state: DamusState, thread: ThreadModel) {
|
||||
self.state = state
|
||||
self._thread = ObservedObject(wrappedValue: thread)
|
||||
let zaps = state.events.get_cache_data(thread.event.id).zaps_model
|
||||
self._zaps = ObservedObject(wrappedValue: zaps)
|
||||
}
|
||||
|
||||
var parent_events: [NostrEvent] {
|
||||
state.events.parent_events(event: thread.event)
|
||||
}
|
||||
@ -22,23 +31,28 @@ struct ThreadView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let top_zap = get_top_zap(events: state.events, evid: thread.event.id)
|
||||
ScrollViewReader { reader in
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
// MARK: - Parents events view
|
||||
ForEach(parent_events, id: \.id) { parent_event in
|
||||
MutedEventView(damus_state: state,
|
||||
event: parent_event,
|
||||
selected: false)
|
||||
.padding(.horizontal)
|
||||
.onTapGesture {
|
||||
thread.set_active_event(parent_event)
|
||||
scroll_to_event(scroller: reader, id: parent_event.id, delay: 0.1, animate: false)
|
||||
if top_zap?.event?.id != parent_event.id {
|
||||
|
||||
MutedEventView(damus_state: state,
|
||||
event: parent_event,
|
||||
selected: false)
|
||||
.padding(.horizontal)
|
||||
.onTapGesture {
|
||||
thread.set_active_event(parent_event)
|
||||
scroll_to_event(scroller: reader, id: parent_event.id, delay: 0.1, animate: false)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding(.top, 4)
|
||||
.padding(.leading, 25 * 2)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding(.top, 4)
|
||||
.padding(.leading, 25 * 2)
|
||||
}.background(GeometryReader { geometry in
|
||||
// get the height and width of the EventView view
|
||||
let eventHeight = geometry.frame(in: .global).height
|
||||
@ -59,20 +73,27 @@ struct ThreadView: View {
|
||||
)
|
||||
.id(self.thread.event.id)
|
||||
|
||||
if let top_zap {
|
||||
ZapEvent(damus: state, zap: top_zap, is_top_zap: true)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
ForEach(child_events, id: \.id) { child_event in
|
||||
MutedEventView(
|
||||
damus_state: state,
|
||||
event: child_event,
|
||||
selected: false
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.onTapGesture {
|
||||
thread.set_active_event(child_event)
|
||||
scroll_to_event(scroller: reader, id: child_event.id, delay: 0.1, animate: false)
|
||||
if top_zap?.event?.id != child_event.id {
|
||||
MutedEventView(
|
||||
damus_state: state,
|
||||
event: child_event,
|
||||
selected: false
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.onTapGesture {
|
||||
thread.set_active_event(child_event)
|
||||
scroll_to_event(scroller: reader, id: child_event.id, delay: 0.1, animate: false)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding([.top], 4)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding([.top], 4)
|
||||
}
|
||||
}
|
||||
}.navigationBarTitle(NSLocalizedString("Thread", comment: "Navigation bar title for note thread."))
|
||||
|
@ -22,8 +22,8 @@ struct ZapsView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
ForEach(zaps.zaps, id: \.request.id) { zap in
|
||||
ZapEvent(damus: state, zap: zap)
|
||||
ForEach(zaps.zaps, id: \.request.ev.id) { zap in
|
||||
ZapEvent(damus: state, zap: zap, is_top_zap: false)
|
||||
.padding([.horizontal])
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user