A fairly large change that replaces Zaps in the codebase with "Zapping" which is a tagged union consisting of a resolved Zap and a Pending Zap. These are both counted as Zaps everywhere in Damus, except pending zaps can be cancelled (most of the time).
231 lines
6.5 KiB
Swift
231 lines
6.5 KiB
Swift
//
|
|
// TextEvent.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2023-02-03.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct EventViewOptions: OptionSet {
|
|
let rawValue: UInt32
|
|
|
|
static let no_action_bar = EventViewOptions(rawValue: 1 << 0)
|
|
static let no_replying_to = EventViewOptions(rawValue: 1 << 1)
|
|
static let wide = EventViewOptions(rawValue: 1 << 3)
|
|
static let truncate_content = EventViewOptions(rawValue: 1 << 4)
|
|
static let pad_content = EventViewOptions(rawValue: 1 << 5)
|
|
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 embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
|
|
}
|
|
|
|
struct RelativeTime: View {
|
|
@ObservedObject var time: RelativeTimeModel
|
|
|
|
var body: some View {
|
|
Text(verbatim: "\(time.value)")
|
|
.font(.system(size: 16))
|
|
.foregroundColor(.gray)
|
|
}
|
|
}
|
|
|
|
struct TextEvent: View {
|
|
let damus: DamusState
|
|
let event: NostrEvent
|
|
let pubkey: String
|
|
let options: EventViewOptions
|
|
let evdata: EventData
|
|
|
|
init(damus: DamusState, event: NostrEvent, pubkey: String, options: EventViewOptions) {
|
|
self.damus = damus
|
|
self.event = event
|
|
self.pubkey = pubkey
|
|
self.options = options
|
|
self.evdata = damus.events.get_cache_data(event.id)
|
|
}
|
|
|
|
var has_action_bar: Bool {
|
|
!options.contains(.no_action_bar)
|
|
}
|
|
|
|
var body: some View {
|
|
Group {
|
|
if options.contains(.wide) {
|
|
WideStyle
|
|
} else {
|
|
ThreadedStyle
|
|
}
|
|
}
|
|
.contentShape(Rectangle())
|
|
.id(event.id)
|
|
.frame(maxWidth: .infinity, minHeight: PFP_SIZE)
|
|
.padding([.bottom], 2)
|
|
}
|
|
|
|
func Pfp(is_anon: Bool) -> some View {
|
|
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey, size: options.contains(.small_pfp) ? eventview_pfp_size(.small) : PFP_SIZE )
|
|
}
|
|
|
|
func TopPart(is_anon: Bool) -> some View {
|
|
HStack(alignment: .center, spacing: 0) {
|
|
ProfileName(is_anon: is_anon)
|
|
TimeDot
|
|
RelativeTime(time: self.evdata.relative_time)
|
|
Spacer()
|
|
ContextButton
|
|
}
|
|
.lineLimit(1)
|
|
}
|
|
|
|
var ReplyPart: some View {
|
|
Group {
|
|
if event_is_reply(event, privkey: damus.keypair.privkey) {
|
|
ReplyDescription(event: event, profiles: damus.profiles)
|
|
} else {
|
|
EmptyView()
|
|
}
|
|
}
|
|
}
|
|
|
|
var WideStyle: some View {
|
|
VStack(alignment: .leading) {
|
|
let is_anon = event_is_anonymous(ev: event)
|
|
|
|
HStack(spacing: 10) {
|
|
Pfp(is_anon: is_anon)
|
|
VStack {
|
|
TopPart(is_anon: is_anon)
|
|
ReplyPart
|
|
}
|
|
}
|
|
.padding(.horizontal)
|
|
|
|
EvBody(options: self.options.union(.pad_content))
|
|
|
|
if let mention = get_mention() {
|
|
Mention(mention)
|
|
.padding(.horizontal)
|
|
}
|
|
|
|
if has_action_bar {
|
|
//EmptyRect
|
|
ActionBar
|
|
.padding(.horizontal)
|
|
}
|
|
}
|
|
}
|
|
|
|
var TimeDot: some View {
|
|
Text(verbatim: "⋅")
|
|
.font(.footnote)
|
|
.foregroundColor(.gray)
|
|
}
|
|
|
|
var ContextButton: some View {
|
|
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks, muted_threads: damus.muted_threads)
|
|
.padding([.bottom], 4)
|
|
}
|
|
|
|
func ProfileName(is_anon: Bool) -> some View {
|
|
let profile = damus.profiles.lookup(id: pubkey)
|
|
let pk = is_anon ? "anon" : pubkey
|
|
return EventProfileName(pubkey: pk, profile: profile, damus: damus, size: .normal)
|
|
}
|
|
|
|
func EvBody(options: EventViewOptions) -> some View {
|
|
let show_imgs = should_show_images(settings: damus.settings, contacts: damus.contacts, ev: event, our_pubkey: damus.pubkey)
|
|
return NoteContentView(
|
|
damus_state: damus,
|
|
event: event,
|
|
show_images: show_imgs,
|
|
size: .normal,
|
|
options: options
|
|
)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
func Mention(_ mention: Mention) -> some View {
|
|
return BuilderEventView(damus: damus, event_id: mention.ref.id)
|
|
}
|
|
|
|
var ActionBar: some View {
|
|
return EventActionBar(damus_state: damus, event: event)
|
|
.padding([.top], 4)
|
|
}
|
|
|
|
var EmptyRect: some View {
|
|
return Rectangle().frame(height: 2).opacity(0)
|
|
}
|
|
|
|
func get_mention() -> Mention? {
|
|
if self.options.contains(.nested) {
|
|
return nil
|
|
}
|
|
|
|
return first_eref_mention(ev: event, privkey: damus.keypair.privkey)
|
|
}
|
|
|
|
var ThreadedStyle: some View {
|
|
HStack(alignment: .top) {
|
|
|
|
let is_anon = event_is_anonymous(ev: event)
|
|
VStack {
|
|
Pfp(is_anon: is_anon)
|
|
|
|
Spacer()
|
|
}
|
|
|
|
VStack(alignment: .leading) {
|
|
TopPart(is_anon: is_anon)
|
|
|
|
if !options.contains(.no_replying_to) {
|
|
ReplyPart
|
|
}
|
|
|
|
EvBody(options: self.options)
|
|
|
|
if let mention = get_mention() {
|
|
Mention(mention)
|
|
}
|
|
|
|
if has_action_bar {
|
|
EmptyRect
|
|
ActionBar
|
|
}
|
|
}
|
|
.padding([.leading], 2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func event_has_tag(ev: NostrEvent, tag: String) -> Bool {
|
|
for t in ev.tags {
|
|
if t.count >= 1 && t[0] == tag {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
|
|
func event_is_anonymous(ev: NostrEvent) -> Bool {
|
|
return ev.known_kind == .zap_request && event_has_tag(ev: ev, tag: "anon")
|
|
}
|
|
|
|
struct TextEvent_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
VStack(spacing: 20) {
|
|
TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", options: [])
|
|
.frame(height: 400)
|
|
|
|
TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", options: [.wide])
|
|
.frame(height: 400)
|
|
}
|
|
}
|
|
}
|
|
|