1
0
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:
William Casarin 2023-06-09 10:10:33 +02:00
parent 8f237b47eb
commit 043eb5b436
19 changed files with 258 additions and 140 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
})
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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]
}

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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."))

View File

@ -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])
}
}