@@ -81,6 +81,7 @@
|
|||||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
||||||
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
|
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
|
||||||
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
|
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
|
||||||
|
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */; };
|
||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
||||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
|
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
|
||||||
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
|
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
|
||||||
@@ -200,6 +201,7 @@
|
|||||||
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
||||||
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
|
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
|
||||||
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
|
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
|
||||||
|
4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatroomMetadata.swift; sourceTree = "<group>"; };
|
||||||
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
||||||
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
||||||
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
|
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
4C649843285A952100EAE2B3 /* LocalUserConfig.swift */,
|
4C649843285A952100EAE2B3 /* LocalUserConfig.swift */,
|
||||||
4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */,
|
4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */,
|
||||||
4C216F372871EDE300040376 /* DirectMessageModel.swift */,
|
4C216F372871EDE300040376 /* DirectMessageModel.swift */,
|
||||||
|
4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -673,6 +676,7 @@
|
|||||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
|
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
|
||||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
||||||
4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */,
|
4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */,
|
||||||
|
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */,
|
||||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
||||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||||
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ struct ContentView: View {
|
|||||||
var MaybeThreadView: some View {
|
var MaybeThreadView: some View {
|
||||||
Group {
|
Group {
|
||||||
if let evid = self.active_event_id {
|
if let evid = self.active_event_id {
|
||||||
let thread_model = ThreadModel(evid: evid, pool: damus_state!.pool, privkey: damus_state!.keypair.privkey)
|
let thread_model = ThreadModel(evid: evid, damus_state: damus_state!)
|
||||||
ThreadView(thread: thread_model, damus: damus_state!, is_chatroom: false)
|
ThreadView(thread: thread_model, damus: damus_state!, is_chatroom: false)
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
|
|||||||
15
damus/Models/ChatroomMetadata.swift
Normal file
15
damus/Models/ChatroomMetadata.swift
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// ChatroomMetadata.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-09-07.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
struct ChatroomMetadata: Decodable {
|
||||||
|
let name: String?
|
||||||
|
let about: String?
|
||||||
|
let picture: String?
|
||||||
|
}
|
||||||
@@ -34,6 +34,8 @@ class HomeModel: ObservableObject {
|
|||||||
var damus_state: DamusState
|
var damus_state: DamusState
|
||||||
|
|
||||||
var has_event: [String: Set<String>] = [:]
|
var has_event: [String: Set<String>] = [:]
|
||||||
|
var deleted_events: Set<String> = Set()
|
||||||
|
var channels: [String: NostrEvent] = [:]
|
||||||
var last_event_of_kind: [String: [Int: NostrEvent]] = [:]
|
var last_event_of_kind: [String: [Int: NostrEvent]] = [:]
|
||||||
var done_init: Bool = false
|
var done_init: Bool = false
|
||||||
|
|
||||||
@@ -100,9 +102,32 @@ class HomeModel: ObservableObject {
|
|||||||
case .dm:
|
case .dm:
|
||||||
handle_dm(ev)
|
handle_dm(ev)
|
||||||
case .delete:
|
case .delete:
|
||||||
break
|
handle_delete_event(ev)
|
||||||
|
case .channel_create:
|
||||||
|
handle_channel_create(ev)
|
||||||
|
case .channel_meta:
|
||||||
|
handle_channel_meta(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handle_channel_create(_ ev: NostrEvent) {
|
||||||
|
guard ev.is_valid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.channels[ev.id] = ev
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_channel_meta(_ ev: NostrEvent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_delete_event(_ ev: NostrEvent) {
|
||||||
|
guard ev.is_valid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.deleted_events.insert(ev.id)
|
||||||
|
}
|
||||||
|
|
||||||
func handle_contact_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
func handle_contact_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
||||||
process_contact_event(pool: damus_state.pool, contacts: damus_state.contacts, pubkey: damus_state.pubkey, ev: ev)
|
process_contact_event(pool: damus_state.pool, contacts: damus_state.contacts, pubkey: damus_state.pubkey, ev: ev)
|
||||||
@@ -119,11 +144,14 @@ class HomeModel: ObservableObject {
|
|||||||
func handle_boost_event(sub_id: String, _ ev: NostrEvent) {
|
func handle_boost_event(sub_id: String, _ ev: NostrEvent) {
|
||||||
var boost_ev_id = ev.last_refid()?.ref_id
|
var boost_ev_id = ev.last_refid()?.ref_id
|
||||||
|
|
||||||
// CHECK SIGS ON THESE
|
|
||||||
if let inner_ev = ev.inner_event {
|
if let inner_ev = ev.inner_event {
|
||||||
boost_ev_id = inner_ev.id
|
boost_ev_id = inner_ev.id
|
||||||
|
|
||||||
|
guard inner_ev.is_valid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if inner_ev.kind == 1 {
|
if inner_ev.is_textlike {
|
||||||
handle_text_event(sub_id: sub_id, ev)
|
handle_text_event(sub_id: sub_id, ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +287,7 @@ class HomeModel: ObservableObject {
|
|||||||
// TODO: separate likes?
|
// TODO: separate likes?
|
||||||
var home_filter = NostrFilter.filter_kinds([
|
var home_filter = NostrFilter.filter_kinds([
|
||||||
NostrKind.text.rawValue,
|
NostrKind.text.rawValue,
|
||||||
|
NostrKind.chat.rawValue,
|
||||||
NostrKind.like.rawValue,
|
NostrKind.like.rawValue,
|
||||||
NostrKind.boost.rawValue,
|
NostrKind.boost.rawValue,
|
||||||
])
|
])
|
||||||
@@ -268,6 +297,7 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
var notifications_filter = NostrFilter.filter_kinds([
|
var notifications_filter = NostrFilter.filter_kinds([
|
||||||
NostrKind.text.rawValue,
|
NostrKind.text.rawValue,
|
||||||
|
NostrKind.chat.rawValue,
|
||||||
NostrKind.like.rawValue,
|
NostrKind.like.rawValue,
|
||||||
NostrKind.boost.rawValue,
|
NostrKind.boost.rawValue,
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ class ProfileModel: ObservableObject {
|
|||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
var text_filter = NostrFilter.filter_kinds([
|
var text_filter = NostrFilter.filter_kinds([
|
||||||
NostrKind.text.rawValue
|
NostrKind.text.rawValue,
|
||||||
|
NostrKind.chat.rawValue,
|
||||||
])
|
])
|
||||||
|
|
||||||
var profile_filter = NostrFilter.filter_kinds([
|
var profile_filter = NostrFilter.filter_kinds([
|
||||||
@@ -75,7 +76,7 @@ class ProfileModel: ObservableObject {
|
|||||||
if seen_event.contains(ev.id) {
|
if seen_event.contains(ev.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ev.known_kind == .text || ev.known_kind == .boost {
|
if ev.is_textlike || ev.known_kind == .boost {
|
||||||
let _ = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at})
|
let _ = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at})
|
||||||
} else if ev.known_kind == .contacts {
|
} else if ev.known_kind == .contacts {
|
||||||
handle_profile_contact_event(ev)
|
handle_profile_contact_event(ev)
|
||||||
|
|||||||
@@ -40,16 +40,6 @@ class SearchHomeModel: ObservableObject {
|
|||||||
damus_state.pool.unsubscribe(sub_id: base_subid)
|
damus_state.pool.unsubscribe(sub_id: base_subid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_profiles(relay_id: String) {
|
|
||||||
var filter = NostrFilter.filter_profiles
|
|
||||||
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events)
|
|
||||||
filter.authors = authors
|
|
||||||
|
|
||||||
if !authors.isEmpty {
|
|
||||||
damus_state.pool.subscribe(sub_id: profiles_subid, filters: [filter], handler: handle_event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
|
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
|
||||||
switch conn_ev {
|
switch conn_ev {
|
||||||
case .ws_event:
|
case .ws_event:
|
||||||
@@ -68,8 +58,6 @@ class SearchHomeModel: ObservableObject {
|
|||||||
let _ = insert_uniq_sorted_event(events: &events, new_ev: ev) {
|
let _ = insert_uniq_sorted_event(events: &events, new_ev: ev) {
|
||||||
$0.created_at > $1.created_at
|
$0.created_at > $1.created_at
|
||||||
}
|
}
|
||||||
} else if ev.known_kind == .metadata {
|
|
||||||
process_metadata_event(image_cache: damus_state.image_cache, profiles: damus_state.profiles, ev: ev)
|
|
||||||
}
|
}
|
||||||
case .notice(let msg):
|
case .notice(let msg):
|
||||||
print("search home notice: \(msg)")
|
print("search home notice: \(msg)")
|
||||||
@@ -77,9 +65,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
if sub_id == self.base_subid {
|
if sub_id == self.base_subid {
|
||||||
load_profiles(relay_id: relay_id)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events, damus_state: damus_state)
|
||||||
} else if sub_id == self.profiles_subid {
|
|
||||||
damus_state.pool.unsubscribe(sub_id: self.profiles_subid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -115,3 +101,33 @@ func find_profiles_to_fetch(profiles: Profiles, events: [NostrEvent]) -> [String
|
|||||||
|
|
||||||
return Array(pubkeys)
|
return Array(pubkeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent], damus_state: DamusState) {
|
||||||
|
var filter = NostrFilter.filter_profiles
|
||||||
|
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events)
|
||||||
|
filter.authors = authors
|
||||||
|
|
||||||
|
if !authors.isEmpty {
|
||||||
|
print("loading \(authors.count) profiles from \(relay_id)")
|
||||||
|
damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in
|
||||||
|
let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in
|
||||||
|
guard sub_id == profiles_subid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ev.known_kind == .metadata {
|
||||||
|
process_metadata_event(image_cache: damus_state.image_cache, profiles: damus_state.profiles, ev: ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
guard done && sid == profiles_subid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("done loading \(authors.count) profiles from \(relay_id)")
|
||||||
|
damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import Foundation
|
|||||||
class SearchModel: ObservableObject {
|
class SearchModel: ObservableObject {
|
||||||
@Published var events: [NostrEvent] = []
|
@Published var events: [NostrEvent] = []
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
|
@Published var channel_name: String? = nil
|
||||||
|
|
||||||
let pool: RelayPool
|
let pool: RelayPool
|
||||||
var search: NostrFilter
|
var search: NostrFilter
|
||||||
let sub_id = UUID().description
|
let sub_id = UUID().description
|
||||||
@@ -50,10 +52,24 @@ class SearchModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handle_channel_create(_ ev: NostrEvent) {
|
||||||
|
self.channel_name = ev.content
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_channel_meta(_ ev: NostrEvent) {
|
||||||
|
self.channel_name = ev.content
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||||
let done = handle_subid_event(pool: pool, sub_id: sub_id, relay_id: relay_id, ev: ev) { ev in
|
let (_, done) = handle_subid_event(pool: pool, relay_id: relay_id, ev: ev) { sub_id, ev in
|
||||||
if ev.known_kind == .text && ev.should_show_event {
|
if ev.is_textlike && ev.should_show_event {
|
||||||
self.add_event(ev)
|
self.add_event(ev)
|
||||||
|
} else if ev.known_kind == .channel_create {
|
||||||
|
handle_channel_create(ev)
|
||||||
|
} else if ev.known_kind == .channel_meta {
|
||||||
|
handle_channel_meta(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,29 +95,26 @@ func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_subid_event(pool: RelayPool, sub_id: String, relay_id: String, ev: NostrConnectionEvent, handle: (NostrEvent) -> ()) -> Bool {
|
func handle_subid_event(pool: RelayPool, relay_id: String, ev: NostrConnectionEvent, handle: (String, NostrEvent) -> ()) -> (String?, Bool) {
|
||||||
switch ev {
|
switch ev {
|
||||||
case .ws_event:
|
case .ws_event:
|
||||||
break
|
return (nil, false)
|
||||||
|
|
||||||
case .nostr_event(let res):
|
case .nostr_event(let res):
|
||||||
switch res {
|
switch res {
|
||||||
case .event(let ev_subid, let ev):
|
case .event(let ev_subid, let ev):
|
||||||
if ev_subid == sub_id {
|
handle(ev_subid, ev)
|
||||||
handle(ev)
|
return (ev_subid, false)
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case .notice(let note):
|
case .notice(let note):
|
||||||
if note.contains("Too many subscription filters") {
|
if note.contains("Too many subscription filters") {
|
||||||
// TODO: resend filters?
|
// TODO: resend filters?
|
||||||
pool.reconnect(to: [relay_id])
|
pool.reconnect(to: [relay_id])
|
||||||
}
|
}
|
||||||
break
|
return (nil, false)
|
||||||
|
|
||||||
case .eose:
|
case .eose(let subid):
|
||||||
return true
|
return (subid, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ enum InitialEvent {
|
|||||||
|
|
||||||
/// manages the lifetime of a thread
|
/// manages the lifetime of a thread
|
||||||
class ThreadModel: ObservableObject {
|
class ThreadModel: ObservableObject {
|
||||||
let privkey: String?
|
|
||||||
let kind: Int
|
let kind: Int
|
||||||
|
|
||||||
@Published var initial_event: InitialEvent
|
@Published var initial_event: InitialEvent
|
||||||
@Published var events: [NostrEvent] = []
|
@Published var events: [NostrEvent] = []
|
||||||
@Published var event_map: [String: Int] = [:]
|
@Published var event_map: [String: Int] = [:]
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
var replies: ReplyMap = ReplyMap()
|
var replies: ReplyMap = ReplyMap()
|
||||||
|
|
||||||
var event: NostrEvent? {
|
var event: NostrEvent? {
|
||||||
@@ -53,33 +53,32 @@ class ThreadModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pool: RelayPool
|
let damus_state: DamusState
|
||||||
var sub_id = UUID().description
|
|
||||||
|
let profiles_subid = UUID().description
|
||||||
|
var base_subid = UUID().description
|
||||||
|
|
||||||
init(evid: String, pool: RelayPool, privkey: String?) {
|
init(evid: String, damus_state: DamusState) {
|
||||||
self.pool = pool
|
self.damus_state = damus_state
|
||||||
self.initial_event = .event_id(evid)
|
self.initial_event = .event_id(evid)
|
||||||
self.privkey = privkey
|
|
||||||
self.kind = NostrKind.text.rawValue
|
self.kind = NostrKind.text.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
init(event: NostrEvent, pool: RelayPool, privkey: String?) {
|
init(event: NostrEvent, damus_state: DamusState) {
|
||||||
self.pool = pool
|
self.damus_state = damus_state
|
||||||
self.initial_event = .event(event)
|
self.initial_event = .event(event)
|
||||||
self.privkey = privkey
|
|
||||||
self.kind = NostrKind.text.rawValue
|
self.kind = NostrKind.text.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
init(event: NostrEvent, pool: RelayPool, privkey: String?, kind: Int) {
|
init(event: NostrEvent, damus_state: DamusState, kind: Int) {
|
||||||
self.pool = pool
|
self.damus_state = damus_state
|
||||||
self.initial_event = .event(event)
|
self.initial_event = .event(event)
|
||||||
self.privkey = privkey
|
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe() {
|
func unsubscribe() {
|
||||||
self.pool.unsubscribe(sub_id: sub_id)
|
self.damus_state.pool.unsubscribe(sub_id: base_subid)
|
||||||
print("unsubscribing from thread \(initial_event.id) with sub_id \(sub_id)")
|
print("unsubscribing from thread \(initial_event.id) with sub_id \(base_subid)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset_events() {
|
func reset_events() {
|
||||||
@@ -125,19 +124,23 @@ class ThreadModel: ObservableObject {
|
|||||||
case .event(let ev):
|
case .event(let ev):
|
||||||
ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id }
|
ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id }
|
||||||
ref_events.referenced_ids?.append(ev.id)
|
ref_events.referenced_ids?.append(ev.id)
|
||||||
|
ref_events.limit = 50
|
||||||
events_filter.ids = ref_events.referenced_ids!
|
events_filter.ids = ref_events.referenced_ids!
|
||||||
|
events_filter.limit = 100
|
||||||
events_filter.ids?.append(ev.id)
|
events_filter.ids?.append(ev.id)
|
||||||
case .event_id(let evid):
|
case .event_id(let evid):
|
||||||
events_filter.ids = [evid]
|
events_filter.ids = [evid]
|
||||||
|
events_filter.limit = 100
|
||||||
ref_events.referenced_ids = [evid]
|
ref_events.referenced_ids = [evid]
|
||||||
|
ref_events.limit = 50
|
||||||
}
|
}
|
||||||
|
|
||||||
//likes_filter.ids = ref_events.referenced_ids!
|
//likes_filter.ids = ref_events.referenced_ids!
|
||||||
|
|
||||||
print("subscribing to thread \(initial_event.id) with sub_id \(sub_id)")
|
print("subscribing to thread \(initial_event.id) with sub_id \(base_subid)")
|
||||||
pool.register_handler(sub_id: sub_id, handler: handle_event)
|
damus_state.pool.register_handler(sub_id: base_subid, handler: handle_event)
|
||||||
loading = true
|
loading = true
|
||||||
pool.send(.subscribe(.init(filters: [ref_events, events_filter], sub_id: sub_id)))
|
damus_state.pool.send(.subscribe(.init(filters: [ref_events, events_filter], sub_id: base_subid)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup(_ event_id: String) -> NostrEvent? {
|
func lookup(_ event_id: String) -> NostrEvent? {
|
||||||
@@ -180,18 +183,41 @@ class ThreadModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handle_channel_meta(_ ev: NostrEvent) {
|
||||||
|
guard let meta: ChatroomMetadata = decode_json(ev.content) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(.chatroom_meta, meta)
|
||||||
|
}
|
||||||
|
|
||||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||||
let done = handle_subid_event(pool: pool, sub_id: sub_id, relay_id: relay_id, ev: ev) { ev in
|
|
||||||
if ev.is_textlike {
|
let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in
|
||||||
self.add_event(ev, privkey: self.privkey)
|
guard sid == base_subid || sid == profiles_subid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ev.known_kind == .metadata {
|
||||||
|
process_metadata_event(image_cache: damus_state.image_cache, profiles: damus_state.profiles, ev: ev)
|
||||||
|
} else if ev.is_textlike {
|
||||||
|
self.add_event(ev, privkey: self.damus_state.keypair.privkey)
|
||||||
|
} else if ev.known_kind == .channel_meta || ev.known_kind == .channel_create {
|
||||||
|
handle_channel_meta(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if done {
|
guard done && (sub_id == base_subid || sub_id == profiles_subid) else {
|
||||||
if (events.contains { ev in ev.id == initial_event.id }) {
|
return
|
||||||
loading = false
|
}
|
||||||
}
|
|
||||||
|
if (events.contains { ev in ev.id == initial_event.id }) {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if sub_id == self.base_subid {
|
||||||
|
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, events: events, damus_state: damus_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,5 +16,7 @@ enum NostrKind: Int {
|
|||||||
case delete = 5
|
case delete = 5
|
||||||
case boost = 6
|
case boost = 6
|
||||||
case like = 7
|
case like = 7
|
||||||
|
case channel_create = 40
|
||||||
|
case channel_meta = 41
|
||||||
case chat = 42
|
case chat = 42
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class ImageCache {
|
|||||||
func insert(_ image: UIImage, key: String) async -> UIImage? {
|
func insert(_ image: UIImage, key: String) async -> UIImage? {
|
||||||
let scale = await UIScreen.main.scale
|
let scale = await UIScreen.main.scale
|
||||||
let size = CGSize(width: PFP_SIZE * scale, height: PFP_SIZE * scale)
|
let size = CGSize(width: PFP_SIZE * scale, height: PFP_SIZE * scale)
|
||||||
|
|
||||||
set_state(key, new_state: .processing)
|
set_state(key, new_state: .processing)
|
||||||
|
|
||||||
let decoded_image = await image.byPreparingThumbnail(ofSize: size)
|
let decoded_image = await image.byPreparingThumbnail(ofSize: size)
|
||||||
|
|||||||
@@ -139,6 +139,12 @@ extension Notification.Name {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Notification.Name {
|
||||||
|
static var chatroom_meta: Notification.Name {
|
||||||
|
return Notification.Name("chatroom_meta")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
static var unfollowed: Notification.Name {
|
static var unfollowed: Notification.Name {
|
||||||
return Notification.Name("unfollowed")
|
return Notification.Name("unfollowed")
|
||||||
|
|||||||
@@ -124,12 +124,11 @@ struct ChatView: View {
|
|||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.id(event.id)
|
.id(event.id)
|
||||||
.frame(minHeight: just_started ? PFP_SIZE : 0)
|
//.frame(minHeight: just_started ? PFP_SIZE : 0)
|
||||||
.padding([.bottom], next_ev == nil ? 30 : 0)
|
.padding([.bottom], 6)
|
||||||
//.border(Color.green)
|
//.border(Color.green)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import SwiftUI
|
|||||||
struct ChatroomView: View {
|
struct ChatroomView: View {
|
||||||
@EnvironmentObject var thread: ThreadModel
|
@EnvironmentObject var thread: ThreadModel
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
@State var once: Bool = false
|
||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
ScrollView(.vertical) {
|
ScrollView(.vertical) {
|
||||||
LazyVStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
let count = thread.events.count
|
let count = thread.events.count
|
||||||
ForEach(Array(zip(thread.events, thread.events.indices)), id: \.0.id) { (ev, ind) in
|
ForEach(Array(zip(thread.events, thread.events.indices)), id: \.0.id) { (ev, ind) in
|
||||||
ChatView(event: thread.events[ind],
|
ChatView(event: thread.events[ind],
|
||||||
@@ -46,6 +47,13 @@ struct ChatroomView: View {
|
|||||||
}
|
}
|
||||||
scroll_to_event(scroller: scroller, id: ev.id, delay: 0, animate: true)
|
scroll_to_event(scroller: scroller, id: ev.id, delay: 0, animate: true)
|
||||||
}
|
}
|
||||||
|
.onChange(of: thread.loading) { _ in
|
||||||
|
guard !thread.loading && !once else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scroll_after_load(thread: thread, proxy: scroller)
|
||||||
|
once = true
|
||||||
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
scroll_to_event(scroller: scroller, id: thread.initial_event.id, delay: 0.1, animate: false)
|
scroll_to_event(scroller: scroller, id: thread.initial_event.id, delay: 0.1, animate: false)
|
||||||
}
|
}
|
||||||
@@ -66,7 +74,7 @@ struct ChatroomView_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let state = test_damus_state()
|
let state = test_damus_state()
|
||||||
ChatroomView(damus: state)
|
ChatroomView(damus: state)
|
||||||
.environmentObject(ThreadModel(evid: "&849ab9bb263ed2819db06e05f1a1a3b72878464e8c7146718a2fc1bf1912f893", pool: state.pool, privkey: state.keypair.privkey))
|
.environmentObject(ThreadModel(evid: "&849ab9bb263ed2819db06e05f1a1a3b72878464e8c7146718a2fc1bf1912f893", damus_state: state))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,28 +104,29 @@ struct EventDetailView: View {
|
|||||||
EndBlock()
|
EndBlock()
|
||||||
}
|
}
|
||||||
.onChange(of: thread.loading) { val in
|
.onChange(of: thread.loading) { val in
|
||||||
scroll_after_load(proxy)
|
scroll_after_load(thread: thread, proxy: proxy)
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
scroll_after_load(proxy)
|
scroll_after_load(thread: thread, proxy: proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Thread")
|
.navigationBarTitle("Thread")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func scroll_after_load(_ proxy: ScrollViewProxy) {
|
|
||||||
if !thread.loading {
|
|
||||||
let id = thread.initial_event.id
|
|
||||||
scroll_to_event(scroller: proxy, id: id, delay: 0.1, animate: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toggle_thread_view() {
|
func toggle_thread_view() {
|
||||||
NotificationCenter.default.post(name: .toggle_thread_view, object: nil)
|
NotificationCenter.default.post(name: .toggle_thread_view, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scroll_after_load(thread: ThreadModel, proxy: ScrollViewProxy) {
|
||||||
|
if !thread.loading {
|
||||||
|
let id = thread.initial_event.id
|
||||||
|
scroll_to_event(scroller: proxy, id: id, delay: 0.1, animate: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
struct EventDetailView_Previews: PreviewProvider {
|
struct EventDetailView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
|||||||
@@ -126,14 +126,14 @@ struct ProfilePicView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
func hex_to_rgb(_ hex: String) -> Color {
|
func hex_to_rgb(_ hex: String) -> Color {
|
||||||
guard hex.count >= 6 else {
|
guard hex.count >= 6 else {
|
||||||
return Color.black
|
return Color.white
|
||||||
}
|
}
|
||||||
|
|
||||||
let arr = Array(hex.utf8)
|
let arr = Array(hex.utf8)
|
||||||
var rgb: [UInt8] = []
|
var rgb: [UInt8] = []
|
||||||
var i: Int = 0
|
var i: Int = arr.count - 12
|
||||||
|
|
||||||
while i < 6 {
|
while i < arr.count {
|
||||||
let cs1 = arr[i]
|
let cs1 = arr[i]
|
||||||
let cs2 = arr[i+1]
|
let cs2 = arr[i+1]
|
||||||
|
|
||||||
|
|||||||
@@ -50,9 +50,6 @@ struct ReplyQuoteView: View {
|
|||||||
.padding(4)
|
.padding(4)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
} else {
|
|
||||||
ProgressView()
|
|
||||||
.progressViewStyle(.circular)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,6 +60,6 @@ struct ReplyQuoteView_Previews: PreviewProvider {
|
|||||||
let s = test_damus_state()
|
let s = test_damus_state()
|
||||||
let quoter = NostrEvent(content: "a\nb\nc", pubkey: "pubkey")
|
let quoter = NostrEvent(content: "a\nb\nc", pubkey: "pubkey")
|
||||||
ReplyQuoteView(privkey: s.keypair.privkey, quoter: quoter, event_id: "pubkey2", image_cache: s.image_cache, profiles: s.profiles)
|
ReplyQuoteView(privkey: s.keypair.privkey, quoter: quoter, event_id: "pubkey2", image_cache: s.image_cache, profiles: s.profiles)
|
||||||
.environmentObject(ThreadModel(event: quoter, pool: s.pool, privkey: s.keypair.privkey))
|
.environmentObject(ThreadModel(event: quoter, damus_state: s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ struct ThreadView: View {
|
|||||||
@StateObject var thread: ThreadModel
|
@StateObject var thread: ThreadModel
|
||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
@State var is_chatroom: Bool
|
@State var is_chatroom: Bool
|
||||||
|
@State var metadata: ChatroomMetadata? = nil
|
||||||
@State var seen_first: Bool = false
|
@State var seen_first: Bool = false
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@@ -20,11 +21,11 @@ struct ThreadView: View {
|
|||||||
Group {
|
Group {
|
||||||
if is_chatroom {
|
if is_chatroom {
|
||||||
ChatroomView(damus: damus)
|
ChatroomView(damus: damus)
|
||||||
.navigationBarTitle("Chat")
|
.navigationBarTitle(metadata?.name ?? "Chat")
|
||||||
.environmentObject(thread)
|
.environmentObject(thread)
|
||||||
} else {
|
} else {
|
||||||
EventDetailView(damus: damus, thread: thread)
|
EventDetailView(damus: damus, thread: thread)
|
||||||
.navigationBarTitle("Thread")
|
.navigationBarTitle(metadata?.name ?? "Thread")
|
||||||
.environmentObject(thread)
|
.environmentObject(thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,13 +36,17 @@ struct ThreadView: View {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
.padding([.leading, .trailing], 6)
|
.padding([.leading, .trailing], 6)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .switched_timeline)) { n in
|
.onReceive(handle_notify(.switched_timeline)) { n in
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .toggle_thread_view)) { _ in
|
.onReceive(handle_notify(.toggle_thread_view)) { _ in
|
||||||
is_chatroom = !is_chatroom
|
is_chatroom = !is_chatroom
|
||||||
//print("is_chatroom: \(is_chatroom)")
|
//print("is_chatroom: \(is_chatroom)")
|
||||||
}
|
}
|
||||||
|
.onReceive(handle_notify(.chatroom_meta)) { n in
|
||||||
|
let meta = n.object as! ChatroomMetadata
|
||||||
|
self.metadata = meta
|
||||||
|
}
|
||||||
.onChange(of: thread.events) { val in
|
.onChange(of: thread.events) { val in
|
||||||
if seen_first {
|
if seen_first {
|
||||||
return
|
return
|
||||||
@@ -72,7 +77,7 @@ struct ThreadView_Previews: PreviewProvider {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func should_show_chatroom(_ ev: NostrEvent) -> Bool {
|
func should_show_chatroom(_ ev: NostrEvent) -> Bool {
|
||||||
if ev.known_kind == .chat {
|
if ev.known_kind == .chat || ev.known_kind == .channel_create {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct InnerTimelineView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
LazyVStack {
|
LazyVStack {
|
||||||
ForEach(events, id: \.id) { (ev: NostrEvent) in
|
ForEach(events, id: \.id) { (ev: NostrEvent) in
|
||||||
let tm = ThreadModel(event: inner_event_or_self(ev: ev), pool: damus.pool, privkey: damus.keypair.privkey)
|
let tm = ThreadModel(event: inner_event_or_self(ev: ev), damus_state: damus)
|
||||||
let is_chatroom = should_show_chatroom(ev)
|
let is_chatroom = should_show_chatroom(ev)
|
||||||
let tv = ThreadView(thread: tm, damus: damus, is_chatroom: is_chatroom)
|
let tv = ThreadView(thread: tm, damus: damus, is_chatroom: is_chatroom)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user