Notifications
Changelog-Added: Add new Notifications View
This commit is contained in:
@@ -12,11 +12,7 @@ struct UserView: View {
|
||||
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) {
|
||||
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
@@ -192,7 +192,7 @@ struct ContentView: View {
|
||||
case .notifications:
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
TimelineView(events: home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true })
|
||||
NotificationsView(state: damus, notifications: home.notifications)
|
||||
}
|
||||
case .dms:
|
||||
DirectMessagesView(damus_state: damus_state!)
|
||||
@@ -615,7 +615,8 @@ struct ContentView: View {
|
||||
settings: UserSettingsStore(),
|
||||
relay_filters: relay_filters,
|
||||
relay_metadata: metadatas,
|
||||
drafts: Drafts()
|
||||
drafts: Drafts(),
|
||||
events: EventCache()
|
||||
)
|
||||
home.damus_state = self.damus_state!
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ struct DamusState {
|
||||
let relay_filters: RelayFilters
|
||||
let relay_metadata: RelayMetadatas
|
||||
let drafts: Drafts
|
||||
let events: EventCache
|
||||
|
||||
var pubkey: String {
|
||||
return keypair.pubkey
|
||||
@@ -32,9 +33,8 @@ struct DamusState {
|
||||
var is_privkey_user: Bool {
|
||||
keypair.privkey != nil
|
||||
}
|
||||
|
||||
|
||||
static var empty: DamusState {
|
||||
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts())
|
||||
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class EventsModel: ObservableObject {
|
||||
case .notice(_):
|
||||
break
|
||||
case .eose(_):
|
||||
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, events: events, damus_state: state)
|
||||
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,22 +50,18 @@ class HomeModel: ObservableObject {
|
||||
let profiles_subid = UUID().description
|
||||
|
||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||
@Published var notifications: EventHolder
|
||||
@Published var notifications = NotificationsModel()
|
||||
@Published var dms: DirectMessagesModel
|
||||
@Published var events: EventHolder
|
||||
@Published var events = EventHolder()
|
||||
@Published var loading: Bool = false
|
||||
@Published var signal: SignalModel = SignalModel()
|
||||
|
||||
init() {
|
||||
self.events = EventHolder()
|
||||
self.notifications = EventHolder()
|
||||
self.damus_state = DamusState.empty
|
||||
self.dms = DirectMessagesModel(our_pubkey: "")
|
||||
}
|
||||
|
||||
init(damus_state: DamusState) {
|
||||
self.events = EventHolder()
|
||||
self.notifications = EventHolder()
|
||||
self.damus_state = damus_state
|
||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
||||
self.setup_debouncer()
|
||||
@@ -143,7 +139,7 @@ class HomeModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
if !notifications.insert(ev) {
|
||||
if !notifications.insert_zap(zap) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -229,7 +225,7 @@ class HomeModel: ObservableObject {
|
||||
guard inner_ev.is_valid else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if inner_ev.is_textlike {
|
||||
handle_text_event(sub_id: sub_id, ev)
|
||||
}
|
||||
@@ -255,12 +251,11 @@ class HomeModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
// CHECK SIGS ON THESE
|
||||
|
||||
switch damus_state.likes.add_event(ev, target: e.ref_id) {
|
||||
case .already_counted:
|
||||
break
|
||||
case .success(let n):
|
||||
handle_notification(ev: ev)
|
||||
let liked = Counted(event: ev, id: e.ref_id, total: n)
|
||||
notify(.liked, liked)
|
||||
notify(.update_stats, e.ref_id)
|
||||
@@ -320,9 +315,9 @@ class HomeModel: ObservableObject {
|
||||
if sub_id == dms_subid {
|
||||
var dms = dms.dms.flatMap { $0.1.events }
|
||||
dms.append(contentsOf: incoming_dms)
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: dms, damus_state: damus_state)
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state)
|
||||
} else if sub_id == notifications_subid {
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: notifications.all_events, damus_state: damus_state)
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state)
|
||||
}
|
||||
|
||||
self.loading = false
|
||||
@@ -375,7 +370,6 @@ class HomeModel: ObservableObject {
|
||||
// TODO: separate likes?
|
||||
var home_filter = NostrFilter.filter_kinds([
|
||||
NostrKind.text.rawValue,
|
||||
NostrKind.chat.rawValue,
|
||||
NostrKind.like.rawValue,
|
||||
NostrKind.boost.rawValue,
|
||||
])
|
||||
@@ -385,7 +379,6 @@ class HomeModel: ObservableObject {
|
||||
|
||||
var notifications_filter = NostrFilter.filter_kinds([
|
||||
NostrKind.text.rawValue,
|
||||
NostrKind.chat.rawValue,
|
||||
NostrKind.like.rawValue,
|
||||
NostrKind.boost.rawValue,
|
||||
NostrKind.zap.rawValue,
|
||||
@@ -461,7 +454,12 @@ class HomeModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
if !notifications.insert(ev) {
|
||||
damus_state.events.insert(ev)
|
||||
if let inner_ev = ev.inner_event {
|
||||
damus_state.events.insert(inner_ev)
|
||||
}
|
||||
|
||||
if !notifications.insert_event(ev) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -484,6 +482,8 @@ class HomeModel: ObservableObject {
|
||||
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
||||
return
|
||||
}
|
||||
|
||||
damus_state.events.insert(ev)
|
||||
|
||||
if sub_id == home_subid {
|
||||
insert_home_event(ev)
|
||||
|
||||
32
damus/Models/Notifications/EventGroup.swift
Normal file
32
damus/Models/Notifications/EventGroup.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// ReactionGroup.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class EventGroup {
|
||||
var events: [NostrEvent]
|
||||
|
||||
var last_event_at: Int64 {
|
||||
guard let first = self.events.first else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return first.created_at
|
||||
}
|
||||
|
||||
init() {
|
||||
self.events = []
|
||||
}
|
||||
|
||||
init(events: [NostrEvent]) {
|
||||
self.events = events
|
||||
}
|
||||
|
||||
func insert(_ ev: NostrEvent) -> Bool {
|
||||
return insert_uniq_sorted_event_created(events: &events, new_ev: ev)
|
||||
}
|
||||
}
|
||||
53
damus/Models/Notifications/ZapGroup.swift
Normal file
53
damus/Models/Notifications/ZapGroup.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// ZapGroup.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ZapGroup {
|
||||
var zaps: [Zap]
|
||||
var msat_total: Int64
|
||||
var zappers: Set<String>
|
||||
|
||||
var last_event_at: Int64 {
|
||||
guard let first = zaps.first else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return first.event.created_at
|
||||
}
|
||||
|
||||
func zap_requests() -> [NostrEvent] {
|
||||
zaps.map { z in z.request.ev }
|
||||
}
|
||||
|
||||
init(zaps: [Zap]) {
|
||||
self.zaps = zaps
|
||||
self.msat_total = 0
|
||||
self.zappers = Set()
|
||||
}
|
||||
|
||||
init() {
|
||||
self.zaps = []
|
||||
self.msat_total = 0
|
||||
self.zappers = Set()
|
||||
}
|
||||
|
||||
func insert(_ zap: Zap) -> Bool {
|
||||
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
|
||||
return false
|
||||
}
|
||||
|
||||
msat_total += zap.invoice.amount
|
||||
|
||||
if !zappers.contains(zap.request.ev.pubkey) {
|
||||
zappers.insert(zap.request.ev.pubkey)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
294
damus/Models/NotificationsModel.swift
Normal file
294
damus/Models/NotificationsModel.swift
Normal file
@@ -0,0 +1,294 @@
|
||||
//
|
||||
// NotificationsModel.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum NotificationItem {
|
||||
case repost(String, EventGroup)
|
||||
case reaction(String, EventGroup)
|
||||
case profile_zap(ZapGroup)
|
||||
case event_zap(String, ZapGroup)
|
||||
case reply(NostrEvent)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .repost(let evid, _):
|
||||
return "repost_" + evid
|
||||
case .reaction(let evid, _):
|
||||
return "reaction_" + evid
|
||||
case .profile_zap:
|
||||
return "profile_zap"
|
||||
case .event_zap(let evid, _):
|
||||
return "event_zap_" + evid
|
||||
case .reply(let ev):
|
||||
return "reply_" + ev.id
|
||||
}
|
||||
}
|
||||
|
||||
var last_event_at: Int64 {
|
||||
switch self {
|
||||
case .reaction(_, let evgrp):
|
||||
return evgrp.last_event_at
|
||||
case .repost(_, let evgrp):
|
||||
return evgrp.last_event_at
|
||||
case .profile_zap(let zapgrp):
|
||||
return zapgrp.last_event_at
|
||||
case .event_zap(_, let zapgrp):
|
||||
return zapgrp.last_event_at
|
||||
case .reply(let reply):
|
||||
return reply.created_at
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationsModel: ObservableObject {
|
||||
var incoming_zaps: [Zap]
|
||||
var incoming_events: [NostrEvent]
|
||||
var should_queue: Bool
|
||||
|
||||
// mappings from events to
|
||||
var zaps: [String: ZapGroup]
|
||||
var profile_zaps: ZapGroup
|
||||
var reactions: [String: EventGroup]
|
||||
var reposts: [String: EventGroup]
|
||||
var replies: [NostrEvent]
|
||||
var has_reply: Set<String>
|
||||
|
||||
@Published var notifications: [NotificationItem]
|
||||
|
||||
init() {
|
||||
self.zaps = [:]
|
||||
self.reactions = [:]
|
||||
self.reposts = [:]
|
||||
self.replies = []
|
||||
self.has_reply = Set()
|
||||
self.should_queue = true
|
||||
self.incoming_zaps = []
|
||||
self.incoming_events = []
|
||||
self.profile_zaps = ZapGroup()
|
||||
self.notifications = []
|
||||
}
|
||||
|
||||
func uniq_pubkeys() -> [String] {
|
||||
var pks = Set<String>()
|
||||
|
||||
for ev in incoming_events {
|
||||
pks.insert(ev.pubkey)
|
||||
}
|
||||
|
||||
for grp in reposts {
|
||||
for ev in grp.value.events {
|
||||
pks.insert(ev.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
for ev in replies {
|
||||
pks.insert(ev.pubkey)
|
||||
}
|
||||
|
||||
for zap in incoming_zaps {
|
||||
pks.insert(zap.request.ev.pubkey)
|
||||
}
|
||||
|
||||
return Array(pks)
|
||||
}
|
||||
|
||||
func build_notifications() -> [NotificationItem] {
|
||||
var notifs: [NotificationItem] = []
|
||||
|
||||
for el in zaps {
|
||||
let evid = el.key
|
||||
let zapgrp = el.value
|
||||
|
||||
let notif: NotificationItem = .event_zap(evid, zapgrp)
|
||||
notifs.append(notif)
|
||||
}
|
||||
|
||||
if !profile_zaps.zaps.isEmpty {
|
||||
notifs.append(.profile_zap(profile_zaps))
|
||||
}
|
||||
|
||||
for el in reposts {
|
||||
let evid = el.key
|
||||
let evgrp = el.value
|
||||
|
||||
notifs.append(.repost(evid, evgrp))
|
||||
}
|
||||
|
||||
for el in reactions {
|
||||
let evid = el.key
|
||||
let evgrp = el.value
|
||||
|
||||
notifs.append(.reaction(evid, evgrp))
|
||||
}
|
||||
|
||||
for reply in replies {
|
||||
notifs.append(.reply(reply))
|
||||
}
|
||||
|
||||
notifs.sort { $0.last_event_at > $1.last_event_at }
|
||||
return notifs
|
||||
}
|
||||
|
||||
|
||||
private func insert_repost(_ ev: NostrEvent) -> Bool {
|
||||
guard let reposted_ev = ev.inner_event else {
|
||||
return false
|
||||
}
|
||||
|
||||
let id = reposted_ev.id
|
||||
|
||||
if let evgrp = self.reposts[id] {
|
||||
return evgrp.insert(ev)
|
||||
} else {
|
||||
let evgrp = EventGroup()
|
||||
self.reposts[id] = evgrp
|
||||
return evgrp.insert(ev)
|
||||
}
|
||||
}
|
||||
|
||||
private func insert_text(_ ev: NostrEvent) -> Bool {
|
||||
guard !has_reply.contains(ev.id) else {
|
||||
return false
|
||||
}
|
||||
|
||||
has_reply.insert(ev.id)
|
||||
replies.append(ev)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func insert_reaction(_ ev: NostrEvent) -> Bool {
|
||||
guard let ref_id = ev.referenced_ids.last else {
|
||||
return false
|
||||
}
|
||||
|
||||
let id = ref_id.id
|
||||
|
||||
if let evgrp = self.reactions[id] {
|
||||
return evgrp.insert(ev)
|
||||
} else {
|
||||
let evgrp = EventGroup()
|
||||
self.reactions[id] = evgrp
|
||||
return evgrp.insert(ev)
|
||||
}
|
||||
}
|
||||
|
||||
private func insert_event_immediate(_ ev: NostrEvent) -> Bool {
|
||||
if ev.known_kind == .boost {
|
||||
return insert_repost(ev)
|
||||
} else if ev.known_kind == .like {
|
||||
return insert_reaction(ev)
|
||||
} else if ev.known_kind == .text {
|
||||
return insert_text(ev)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func insert_zap_immediate(_ zap: Zap) -> Bool {
|
||||
switch zap.target {
|
||||
case .note(let notezt):
|
||||
let id = notezt.note_id
|
||||
if let zapgrp = self.zaps[notezt.note_id] {
|
||||
return zapgrp.insert(zap)
|
||||
} else {
|
||||
let zapgrp = ZapGroup()
|
||||
self.zaps[id] = zapgrp
|
||||
return zapgrp.insert(zap)
|
||||
}
|
||||
|
||||
case .profile:
|
||||
return profile_zaps.insert(zap)
|
||||
}
|
||||
}
|
||||
|
||||
func insert_event(_ ev: NostrEvent) -> Bool {
|
||||
if should_queue {
|
||||
return insert_uniq_sorted_event_created(events: &incoming_events, new_ev: ev)
|
||||
}
|
||||
|
||||
if insert_event_immediate(ev) {
|
||||
self.notifications = build_notifications()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func insert_zap(_ zap: Zap) -> Bool {
|
||||
if should_queue {
|
||||
return insert_uniq_sorted_zap_by_created(zaps: &incoming_zaps, new_zap: zap)
|
||||
}
|
||||
|
||||
if insert_zap_immediate(zap) {
|
||||
self.notifications = build_notifications()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filter(_ isIncluded: (NostrEvent) -> Bool) {
|
||||
var changed = false
|
||||
var count = 0
|
||||
|
||||
count = incoming_events.count
|
||||
incoming_events = incoming_events.filter(isIncluded)
|
||||
changed = changed || incoming_events.count != count
|
||||
|
||||
count = profile_zaps.zaps.count
|
||||
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request.ev) }
|
||||
changed = changed || profile_zaps.zaps.count != count
|
||||
|
||||
for el in reactions {
|
||||
count = el.value.events.count
|
||||
el.value.events = el.value.events.filter(isIncluded)
|
||||
changed = changed || el.value.events.count != count
|
||||
}
|
||||
|
||||
for el in reposts {
|
||||
count = el.value.events.count
|
||||
el.value.events = el.value.events.filter(isIncluded)
|
||||
changed = changed || el.value.events.count != count
|
||||
}
|
||||
|
||||
for el in zaps {
|
||||
count = el.value.zaps.count
|
||||
el.value.zaps = el.value.zaps.filter {
|
||||
isIncluded($0.request.ev)
|
||||
}
|
||||
changed = changed || el.value.zaps.count != count
|
||||
}
|
||||
|
||||
count = replies.count
|
||||
replies = replies.filter(isIncluded)
|
||||
changed = changed || replies.count != count
|
||||
|
||||
if changed {
|
||||
self.notifications = build_notifications()
|
||||
}
|
||||
}
|
||||
|
||||
func flush() -> Bool {
|
||||
var inserted = false
|
||||
|
||||
for zap in incoming_zaps {
|
||||
inserted = insert_zap_immediate(zap) || inserted
|
||||
}
|
||||
|
||||
for event in incoming_events {
|
||||
inserted = insert_event_immediate(event) || inserted
|
||||
}
|
||||
|
||||
if inserted {
|
||||
self.notifications = build_notifications()
|
||||
}
|
||||
|
||||
return inserted
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class SearchHomeModel: ObservableObject {
|
||||
// global events are not realtime
|
||||
unsubscribe(to: relay_id)
|
||||
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events.all_events, damus_state: damus_state)
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state)
|
||||
}
|
||||
|
||||
|
||||
@@ -98,8 +98,31 @@ func find_profiles_to_fetch_pk(profiles: Profiles, event_pubkeys: [String]) -> [
|
||||
|
||||
return Array(pubkeys)
|
||||
}
|
||||
|
||||
func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad) -> [String] {
|
||||
switch load {
|
||||
case .from_events(let events):
|
||||
return find_profiles_to_fetch_from_events(profiles: profiles, events: events)
|
||||
case .from_keys(let pks):
|
||||
return find_profiles_to_fetch_from_keys(profiles: profiles, pks: pks)
|
||||
}
|
||||
}
|
||||
|
||||
func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [String]) -> [String] {
|
||||
var pubkeys = Set<String>()
|
||||
|
||||
func find_profiles_to_fetch(profiles: Profiles, events: [NostrEvent]) -> [String] {
|
||||
for pk in pks {
|
||||
if profiles.lookup(id: pk) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pubkeys.insert(pk)
|
||||
}
|
||||
|
||||
return Array(pubkeys)
|
||||
}
|
||||
|
||||
func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]) -> [String] {
|
||||
var pubkeys = Set<String>()
|
||||
|
||||
for ev in events {
|
||||
@@ -113,9 +136,14 @@ func find_profiles_to_fetch(profiles: Profiles, events: [NostrEvent]) -> [String
|
||||
return Array(pubkeys)
|
||||
}
|
||||
|
||||
func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent], damus_state: DamusState) {
|
||||
enum PubkeysToLoad {
|
||||
case from_events([NostrEvent])
|
||||
case from_keys([String])
|
||||
}
|
||||
|
||||
func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad, damus_state: DamusState) {
|
||||
var filter = NostrFilter.filter_profiles
|
||||
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events)
|
||||
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load)
|
||||
filter.authors = authors
|
||||
|
||||
guard !authors.isEmpty else {
|
||||
|
||||
@@ -207,7 +207,7 @@ class ThreadModel: ObservableObject {
|
||||
}
|
||||
|
||||
if sub_id == self.base_subid {
|
||||
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, events: events, damus_state: damus_state)
|
||||
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: damus_state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,14 +50,14 @@ class ZapsModel: ObservableObject {
|
||||
break
|
||||
case .eose:
|
||||
let events = self.zaps.map { $0.request.ev }
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events, damus_state: state)
|
||||
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] {
|
||||
if insert_uniq_sorted_zap(zaps: &zaps, new_zap: zap) {
|
||||
if insert_uniq_sorted_zap_by_amount(zaps: &zaps, new_zap: zap) {
|
||||
objectWillChange.send()
|
||||
}
|
||||
} else {
|
||||
@@ -71,7 +71,7 @@ class ZapsModel: ObservableObject {
|
||||
|
||||
state.zaps.add_zap(zap: zap)
|
||||
|
||||
if insert_uniq_sorted_zap(zaps: &zaps, new_zap: zap) {
|
||||
if insert_uniq_sorted_zap_by_amount(zaps: &zaps, new_zap: zap) {
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,9 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
||||
return decrypted(privkey: privkey) ?? "*failed to decrypt content*"
|
||||
}
|
||||
|
||||
return content
|
||||
|
||||
/*
|
||||
switch validity {
|
||||
case .ok:
|
||||
return content
|
||||
@@ -176,6 +179,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
||||
case .bad_sig:
|
||||
return content + "\n\n*WARNING: invalid signature, could be forged!*"
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
var description: String {
|
||||
|
||||
27
damus/Util/EventCache.swift
Normal file
27
damus/Util/EventCache.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// EventCache.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class EventCache {
|
||||
private var events: [String: NostrEvent]
|
||||
|
||||
func lookup(_ evid: String) -> NostrEvent? {
|
||||
return events[evid]
|
||||
}
|
||||
|
||||
func insert(_ ev: NostrEvent) {
|
||||
guard events[ev.id] == nil else {
|
||||
return
|
||||
}
|
||||
events[ev.id] = ev
|
||||
}
|
||||
|
||||
init() {
|
||||
self.events = [:]
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class EventHolder: ObservableObject {
|
||||
private var has_event: Set<String>
|
||||
@Published var events: [NostrEvent]
|
||||
@Published var incoming: [NostrEvent]
|
||||
@Published var should_queue: Bool
|
||||
var should_queue: Bool
|
||||
|
||||
var queued: Int {
|
||||
return incoming.count
|
||||
|
||||
@@ -38,8 +38,7 @@ func insert_uniq_by_pubkey(events: inout [NostrEvent], new_ev: NostrEvent, cmp:
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||
func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap, cmp: (Zap, Zap) -> Bool) -> Bool {
|
||||
var i: Int = 0
|
||||
|
||||
for zap in zaps {
|
||||
@@ -48,7 +47,7 @@ func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if new_zap.invoice.amount > zap.invoice.amount {
|
||||
if cmp(new_zap, zap) {
|
||||
zaps.insert(new_zap, at: i)
|
||||
return true
|
||||
}
|
||||
@@ -59,6 +58,19 @@ func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insert_uniq_sorted_zap_by_created(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||
return insert_uniq_sorted_zap(zaps: &zaps, new_zap: new_zap) { (a, b) in
|
||||
a.event.created_at > b.event.created_at
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insert_uniq_sorted_zap_by_amount(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||
return insert_uniq_sorted_zap(zaps: &zaps, new_zap: new_zap) { (a, b) in
|
||||
a.invoice.amount > b.invoice.amount
|
||||
}
|
||||
}
|
||||
|
||||
func insert_uniq_sorted_event_created(events: inout [NostrEvent], new_ev: NostrEvent) -> Bool {
|
||||
return insert_uniq_sorted_event(events: &events, new_ev: new_ev) {
|
||||
|
||||
@@ -36,7 +36,7 @@ class Zaps {
|
||||
if our_zaps[note_target.note_id] == nil {
|
||||
our_zaps[note_target.note_id] = [zap]
|
||||
} else {
|
||||
insert_uniq_sorted_zap(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
|
||||
insert_uniq_sorted_zap_by_amount(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
|
||||
}
|
||||
case .profile(_):
|
||||
break
|
||||
|
||||
@@ -37,9 +37,7 @@ struct DMChatView: View {
|
||||
|
||||
var Header: some View {
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
||||
let fmodel = FollowersModel(damus_state: damus_state, target: pubkey)
|
||||
let profile_page = ProfileView(damus_state: damus_state, profile: pmodel, followers: fmodel)
|
||||
let profile_page = ProfileView(damus_state: damus_state, pubkey: pubkey)
|
||||
return NavigationLink(destination: profile_page) {
|
||||
HStack {
|
||||
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles)
|
||||
|
||||
@@ -61,10 +61,8 @@ struct EventView: View {
|
||||
if event.known_kind == .boost {
|
||||
if let inner_ev = event.inner_event {
|
||||
VStack(alignment: .leading) {
|
||||
let prof_model = ProfileModel(pubkey: event.pubkey, damus: damus)
|
||||
let follow_model = FollowersModel(damus_state: damus, target: event.pubkey)
|
||||
let prof = damus.profiles.lookup(id: event.pubkey)
|
||||
let booster_profile = ProfileView(damus_state: damus, profile: prof_model, followers: follow_model)
|
||||
let booster_profile = ProfileView(damus_state: damus, pubkey: event.pubkey)
|
||||
|
||||
NavigationLink(destination: booster_profile) {
|
||||
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)
|
||||
|
||||
@@ -11,6 +11,14 @@ struct EventBody: View {
|
||||
let damus_state: DamusState
|
||||
let event: NostrEvent
|
||||
let size: EventViewKind
|
||||
let should_show_img: Bool
|
||||
|
||||
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_show_img: Bool? = nil) {
|
||||
self.damus_state = damus_state
|
||||
self.event = event
|
||||
self.size = size
|
||||
self.should_show_img = should_show_img ?? should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
||||
}
|
||||
|
||||
var content: String {
|
||||
event.get_content(damus_state.keypair.privkey)
|
||||
@@ -21,8 +29,6 @@ struct EventBody: View {
|
||||
ReplyDescription(event: event, profiles: damus_state.profiles)
|
||||
}
|
||||
|
||||
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey, booster_pubkey: nil)
|
||||
|
||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
@@ -31,10 +31,7 @@ struct EventProfile: View {
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
VStack {
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
||||
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: FollowersModel(damus_state: damus_state, target: pubkey))
|
||||
|
||||
NavigationLink(destination: pv) {
|
||||
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
|
||||
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,7 @@ struct TextEvent: View {
|
||||
let profile = damus.profiles.lookup(id: pubkey)
|
||||
|
||||
VStack {
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
||||
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
||||
|
||||
NavigationLink(destination: pv) {
|
||||
NavigationLink(destination: ProfileView(damus_state: damus, pubkey: pubkey)) {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus.profiles)
|
||||
}
|
||||
|
||||
|
||||
189
damus/Views/Notifications/EventGroupView.swift
Normal file
189
damus/Views/Notifications/EventGroupView.swift
Normal file
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// RepostGroupView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
enum EventGroupType {
|
||||
case repost(EventGroup)
|
||||
case reaction(EventGroup)
|
||||
case zap(ZapGroup)
|
||||
case profile_zap(ZapGroup)
|
||||
|
||||
var events: [NostrEvent] {
|
||||
switch self {
|
||||
case .repost(let grp):
|
||||
return grp.events
|
||||
case .reaction(let grp):
|
||||
return grp.events
|
||||
case .zap(let zapgrp):
|
||||
return zapgrp.zap_requests()
|
||||
case .profile_zap(let zapgrp):
|
||||
return zapgrp.zap_requests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ReactingTo {
|
||||
case your_post
|
||||
case tagged_in
|
||||
case your_profile
|
||||
}
|
||||
|
||||
func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo {
|
||||
guard let ev else {
|
||||
return .your_profile
|
||||
}
|
||||
|
||||
if ev.pubkey == our_pubkey {
|
||||
return .your_post
|
||||
}
|
||||
|
||||
return .tagged_in
|
||||
}
|
||||
|
||||
func determine_reacting_to_text(_ r: ReactingTo) -> String {
|
||||
switch r {
|
||||
case .tagged_in:
|
||||
return "a post you were tagged in"
|
||||
case .your_post:
|
||||
return "your post"
|
||||
case .your_profile:
|
||||
return "your profile"
|
||||
}
|
||||
}
|
||||
|
||||
func event_author_name(profiles: Profiles, _ ev: NostrEvent) -> String {
|
||||
let alice_pk = ev.pubkey
|
||||
let alice_prof = profiles.lookup(id: alice_pk)
|
||||
return Profile.displayName(profile: alice_prof, pubkey: alice_pk)
|
||||
}
|
||||
|
||||
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?) -> String {
|
||||
let verb = reacting_to_verb(group: group)
|
||||
|
||||
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
|
||||
let target = determine_reacting_to_text(reacting_to)
|
||||
|
||||
if group.events.count == 1 {
|
||||
let ev = group.events.first!
|
||||
let profile = profiles.lookup(id: ev.pubkey)
|
||||
let display_name = Profile.displayName(profile: profile, pubkey: ev.pubkey)
|
||||
return String(format: "%@ is %@ %@", display_name, verb, target)
|
||||
}
|
||||
|
||||
if group.events.count == 2 {
|
||||
let alice_name = event_author_name(profiles: profiles, group.events[0])
|
||||
let bob_name = event_author_name(profiles: profiles, group.events[1])
|
||||
|
||||
return String(format: "%@ and %@ are %@ %@", alice_name, bob_name, verb, target)
|
||||
}
|
||||
|
||||
if group.events.count > 2 {
|
||||
let alice_name = event_author_name(profiles: profiles, group.events.first!)
|
||||
let count = group.events.count - 1
|
||||
|
||||
return String(format: "%@ and %d other people are %@ %@", alice_name, count, verb, target)
|
||||
}
|
||||
|
||||
return "??"
|
||||
}
|
||||
|
||||
func reacting_to_verb(group: EventGroupType) -> String {
|
||||
switch group {
|
||||
case .reaction:
|
||||
return "reacting"
|
||||
case .repost:
|
||||
return "reposting"
|
||||
case .zap: fallthrough
|
||||
case .profile_zap:
|
||||
return "zapping"
|
||||
}
|
||||
}
|
||||
|
||||
struct EventGroupView: View {
|
||||
let state: DamusState
|
||||
let event: NostrEvent?
|
||||
let group: EventGroupType
|
||||
|
||||
var GroupDescription: some View {
|
||||
Text(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event))
|
||||
}
|
||||
|
||||
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
|
||||
let fmt = format_msats_abbrev(zapgrp.msat_total)
|
||||
return VStack(alignment: .center) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.foregroundColor(.orange)
|
||||
Text("\(fmt)")
|
||||
.foregroundColor(Color.orange)
|
||||
}
|
||||
}
|
||||
|
||||
var GroupIcon: some View {
|
||||
Group {
|
||||
switch group {
|
||||
case .repost:
|
||||
Image(systemName: "arrow.2.squarepath")
|
||||
.foregroundColor(Color("DamusGreen"))
|
||||
case .reaction:
|
||||
Image("shaka-full")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(.accentColor)
|
||||
case .profile_zap(let zapgrp):
|
||||
ZapIcon(zapgrp)
|
||||
case .zap(let zapgrp):
|
||||
ZapIcon(zapgrp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
GroupIcon
|
||||
.frame(width: PFP_SIZE + 10)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
ProfilePicturesView(state: state, events: group.events)
|
||||
|
||||
GroupDescription
|
||||
|
||||
if let event {
|
||||
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: event.id)) {
|
||||
Text(event.content)
|
||||
.padding([.top], 1)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.top], 6)
|
||||
}
|
||||
}
|
||||
|
||||
let test_encoded_post = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
|
||||
let test_repost = NostrEvent(id: "", content: test_encoded_post, pubkey: "", kind: 6, tags: [], createdAt: 1)
|
||||
let test_reposts = [test_repost, test_repost]
|
||||
let test_event_group = EventGroup(events: test_reposts)
|
||||
|
||||
struct EventGroupView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
EventGroupView(state: test_damus_state(), event: test_event, group: .repost(test_event_group))
|
||||
.frame(height: 200)
|
||||
.padding()
|
||||
|
||||
EventGroupView(state: test_damus_state(), event: test_event, group: .reaction(test_event_group))
|
||||
.frame(height: 200)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
86
damus/Views/Notifications/NotificationItemView.swift
Normal file
86
damus/Views/Notifications/NotificationItemView.swift
Normal file
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// NotificationItemView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum ShowItem {
|
||||
case show(NostrEvent?)
|
||||
case dontshow(NostrEvent?)
|
||||
}
|
||||
|
||||
func notification_item_event(events: EventCache, notif: NotificationItem) -> ShowItem {
|
||||
switch notif {
|
||||
case .repost(let evid, _):
|
||||
return .dontshow(events.lookup(evid))
|
||||
case .reply(let ev):
|
||||
return .show(ev)
|
||||
case .reaction(let evid, _):
|
||||
return .dontshow(events.lookup(evid))
|
||||
case .event_zap(let evid, _):
|
||||
return .dontshow(events.lookup(evid))
|
||||
case .profile_zap:
|
||||
return .show(nil)
|
||||
}
|
||||
}
|
||||
|
||||
struct NotificationItemView: View {
|
||||
let state: DamusState
|
||||
let item: NotificationItem
|
||||
|
||||
var show_item: ShowItem {
|
||||
notification_item_event(events: state.events, notif: item)
|
||||
}
|
||||
|
||||
func Item(_ ev: NostrEvent?) -> some View {
|
||||
Group {
|
||||
switch item {
|
||||
case .repost(_, let evgrp):
|
||||
EventGroupView(state: state, event: ev, group: .repost(evgrp))
|
||||
|
||||
case .event_zap(_, let zapgrp):
|
||||
EventGroupView(state: state, event: ev, group: .zap(zapgrp))
|
||||
|
||||
case .profile_zap(let grp):
|
||||
EventGroupView(state: state, event: nil, group: .profile_zap(grp))
|
||||
|
||||
case .reaction(_, let evgrp):
|
||||
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
|
||||
|
||||
case .reply(let ev):
|
||||
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: ev.id)) {
|
||||
EventView(damus: state, event: ev, has_action_bar: true)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding([.top,.bottom], 5)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch show_item {
|
||||
case .show(let ev):
|
||||
Item(ev)
|
||||
|
||||
case .dontshow(let ev):
|
||||
if let ev {
|
||||
Item(ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let test_notification_item: NotificationItem = .repost("evid", test_event_group)
|
||||
|
||||
struct NotificationItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NotificationItemView(state: test_damus_state(), item: test_notification_item)
|
||||
}
|
||||
}
|
||||
43
damus/Views/Notifications/NotificationsView.swift
Normal file
43
damus/Views/Notifications/NotificationsView.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// NotificationsView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NotificationsView: View {
|
||||
let state: DamusState
|
||||
@ObservedObject var notifications: NotificationsModel
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { scroller in
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
Color.white.opacity(0)
|
||||
.id("startblock")
|
||||
.frame(height: 5)
|
||||
ForEach(notifications.notifications, id: \.id) { item in
|
||||
NotificationItemView(state: state, item: item)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.onReceive(handle_notify(.scroll_to_top)) { notif in
|
||||
let _ = notifications.flush()
|
||||
self.notifications.should_queue = false
|
||||
scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
let _ = notifications.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NotificationsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NotificationsView(state: test_damus_state(), notifications: NotificationsModel())
|
||||
}
|
||||
}
|
||||
37
damus/Views/Notifications/ProfilePicturesView.swift
Normal file
37
damus/Views/Notifications/ProfilePicturesView.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// ProfilePicturesView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ProfilePicturesView: View {
|
||||
let state: DamusState
|
||||
let events: [NostrEvent]
|
||||
|
||||
@State var nav_target: String? = nil
|
||||
@State var navigating: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(destination: ProfileView(damus_state: state, pubkey: nav_target ?? ""), isActive: $navigating) {
|
||||
EmptyView()
|
||||
}
|
||||
HStack {
|
||||
ForEach(events.prefix(8)) { ev in
|
||||
ProfilePicView(pubkey: ev.pubkey, size: 32.0, highlight: .none, profiles: state.profiles)
|
||||
.onTapGesture {
|
||||
nav_target = ev.pubkey
|
||||
navigating = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfilePicturesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ProfilePicturesView(state: test_damus_state(), events: [test_event, test_event])
|
||||
}
|
||||
}
|
||||
@@ -110,8 +110,6 @@ struct ProfileView: View {
|
||||
static let markdown = Markdown()
|
||||
|
||||
@State private var selected_tab: ProfileTab = .posts
|
||||
@StateObject var profile: ProfileModel
|
||||
@StateObject var followers: FollowersModel
|
||||
@State private var showingEditProfile = false
|
||||
@State var showing_select_wallet: Bool = false
|
||||
@State var is_zoomed: Bool = false
|
||||
@@ -120,6 +118,21 @@ struct ProfileView: View {
|
||||
@State var filter_state : FilterState = .posts
|
||||
@State var yOffset: CGFloat = 0
|
||||
|
||||
@StateObject var profile: ProfileModel
|
||||
@StateObject var followers: FollowersModel
|
||||
|
||||
init(damus_state: DamusState, profile: ProfileModel, followers: FollowersModel) {
|
||||
self.damus_state = damus_state
|
||||
self._profile = StateObject(wrappedValue: profile)
|
||||
self._followers = StateObject(wrappedValue: followers)
|
||||
}
|
||||
|
||||
init(damus_state: DamusState, pubkey: String) {
|
||||
self.damus_state = damus_state
|
||||
self._profile = StateObject(wrappedValue: ProfileModel(pubkey: pubkey, damus: damus_state))
|
||||
self._followers = StateObject(wrappedValue: FollowersModel(damus_state: damus_state, target: pubkey))
|
||||
}
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.openURL) var openURL
|
||||
@@ -459,9 +472,7 @@ struct ProfileView: View {
|
||||
struct ProfileView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let ds = test_damus_state()
|
||||
let followers = FollowersModel(damus_state: ds, target: ds.pubkey)
|
||||
let profile_model = ProfileModel(pubkey: ds.pubkey, damus: ds)
|
||||
ProfileView(damus_state: ds, profile: profile_model, followers: followers)
|
||||
ProfileView(damus_state: ds, pubkey: ds.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user