Merge branch 'master' into event-cleanup
This commit is contained in:
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,3 +1,27 @@
|
|||||||
|
## [0.1.8-5] - 2022-12-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the ability to zoom profile pic on profile page
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improve visual composition of threads
|
||||||
|
- Show npub abbreviations instead of old-style hex
|
||||||
|
- Added search placeholder and larger cancel button
|
||||||
|
- Swap order of Boost and Cancel alert buttons
|
||||||
|
- Rename "Copy Note" to "Copy Note JSON"
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Don't cutoff gifs
|
||||||
|
- Fixed bug where booster's names are not displayed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[0.1.8-5]: https://github.com/damus-io/damus/releases/tag/v0.1.8-5
|
||||||
## [0.1.8-4] - 2022-12-26
|
## [0.1.8-4] - 2022-12-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -132,6 +132,7 @@
|
|||||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
|
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
|
||||||
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
||||||
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
||||||
|
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -311,6 +312,7 @@
|
|||||||
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; };
|
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; };
|
||||||
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; };
|
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; };
|
||||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
|
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
|
||||||
|
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -480,6 +482,7 @@
|
|||||||
4C216F33286F5ACD00040376 /* DMView.swift */,
|
4C216F33286F5ACD00040376 /* DMView.swift */,
|
||||||
4C06670028FC7C5900038D2A /* RelayView.swift */,
|
4C06670028FC7C5900038D2A /* RelayView.swift */,
|
||||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
|
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
|
||||||
|
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -795,6 +798,7 @@
|
|||||||
4C363A9A28283854006E126D /* Reply.swift in Sources */,
|
4C363A9A28283854006E126D /* Reply.swift in Sources */,
|
||||||
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
|
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
|
||||||
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
||||||
|
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
|
||||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||||
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
||||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
||||||
@@ -1031,7 +1035,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1070,7 +1074,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/SparrowTek/Vault",
|
"location" : "https://github.com/SparrowTek/Vault",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "f5707fac23f4a17b3e5ed32dd444f502773615ae",
|
"revision" : "87db56c3c8b6421c65b0745f73e08b0dc56f79d4",
|
||||||
"version" : "1.0.2"
|
"version" : "1.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ struct ImageCarousel: View {
|
|||||||
.loadDiskFileSynchronously()
|
.loadDiskFileSynchronously()
|
||||||
.scaleFactor(UIScreen.main.scale)
|
.scaleFactor(UIScreen.main.scale)
|
||||||
.fade(duration: 0.1)
|
.fade(duration: 0.1)
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fit)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Text(url.absoluteString)
|
Text(url.absoluteString)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,8 +166,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, damus_state: damus_state!)
|
BuildThreadV2View(damus: damus_state!, event_id: evid)
|
||||||
ThreadView(thread: thread_model, damus: damus_state!, is_chatroom: false)
|
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ struct Profile: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func displayName(profile: Profile?, pubkey: String) -> String {
|
static func displayName(profile: Profile?, pubkey: String) -> String {
|
||||||
return profile?.name ?? abbrev_pubkey(pubkey)
|
let pk = bech32_nopre_pubkey(pubkey) ?? pubkey
|
||||||
|
return profile?.name ?? abbrev_pubkey(pk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,19 @@ struct EventId: Identifiable, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
|
class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable {
|
||||||
static func == (lhs: NostrEvent, rhs: NostrEvent) -> Bool {
|
static func == (lhs: NostrEvent, rhs: NostrEvent) -> Bool {
|
||||||
return lhs.id == rhs.id
|
return lhs.id == rhs.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func < (lhs: NostrEvent, rhs: NostrEvent) -> Bool {
|
||||||
|
return lhs.created_at < rhs.created_at
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(id)
|
||||||
|
}
|
||||||
|
|
||||||
var id: String
|
var id: String
|
||||||
var sig: String
|
var sig: String
|
||||||
var tags: [[String]]
|
var tags: [[String]]
|
||||||
@@ -264,7 +272,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
|
|||||||
return (self.flags & 1) != 0
|
return (self.flags & 1) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
init(content: String, pubkey: String, kind: Int = 1, tags: [[String]] = []) {
|
init(content: String, pubkey: String, kind: Int = 1, tags: [[String]] = [], createdAt: Int64 = Int64(Date().timeIntervalSince1970)) {
|
||||||
self.id = ""
|
self.id = ""
|
||||||
self.sig = ""
|
self.sig = ""
|
||||||
|
|
||||||
@@ -272,7 +280,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
|
|||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
self.created_at = Int64(Date().timeIntervalSince1970)
|
self.created_at = createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intiialization statement used to specificy ID
|
/// Intiialization statement used to specificy ID
|
||||||
|
|||||||
@@ -66,6 +66,13 @@ func bech32_pubkey(_ pubkey: String) -> String? {
|
|||||||
return bech32_encode(hrp: "npub", bytes)
|
return bech32_encode(hrp: "npub", bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bech32_nopre_pubkey(_ pubkey: String) -> String? {
|
||||||
|
guard let bytes = hex_decode(pubkey) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bech32_encode(hrp: "", bytes)
|
||||||
|
}
|
||||||
|
|
||||||
func bech32_note_id(_ evid: String) -> String? {
|
func bech32_note_id(_ evid: String) -> String? {
|
||||||
guard let bytes = hex_decode(evid) else {
|
guard let bytes = hex_decode(evid) else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ struct ChatView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.content))
|
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.content), size: .normal)
|
||||||
|
|
||||||
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
||||||
let bar = make_actionbar_model(ev: event, damus: damus_state)
|
let bar = make_actionbar_model(ev: event, damus: damus_state)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ struct DMView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.get_content(damus_state.keypair.privkey)))
|
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal)
|
||||||
.foregroundColor(is_ours ? Color.white : Color.primary)
|
.foregroundColor(is_ours ? Color.white : Color.primary)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))
|
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ struct EventActionBar: View {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
.padding(.top, 1)
|
|
||||||
.alert("Boost", isPresented: $confirm_boost) {
|
.alert("Boost", isPresented: $confirm_boost) {
|
||||||
Button("Cancel") {
|
Button("Cancel") {
|
||||||
confirm_boost = false
|
confirm_boost = false
|
||||||
@@ -139,7 +138,7 @@ struct EventActionBar: View {
|
|||||||
|
|
||||||
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
|
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
|
||||||
Button(action: action) {
|
Button(action: action) {
|
||||||
Label("", systemImage: img)
|
Label(" ", systemImage: img)
|
||||||
.font(.footnote.weight(.medium))
|
.font(.footnote.weight(.medium))
|
||||||
.foregroundColor(col == nil ? Color.gray : col!)
|
.foregroundColor(col == nil ? Color.gray : col!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,90 @@ enum Highlight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum EventViewKind {
|
||||||
|
case small
|
||||||
|
case normal
|
||||||
|
case big
|
||||||
|
case selected
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventviewsize_to_font(_ size: EventViewKind) -> Font {
|
||||||
|
switch size {
|
||||||
|
case .small:
|
||||||
|
return .body
|
||||||
|
case .normal:
|
||||||
|
return .body
|
||||||
|
case .big:
|
||||||
|
return .headline
|
||||||
|
case .selected:
|
||||||
|
return .custom("selected", size: 21.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BuilderEventView: View {
|
||||||
|
let damus: DamusState
|
||||||
|
let event_id: String
|
||||||
|
@State var event: NostrEvent?
|
||||||
|
@State var subscription_uuid: String = UUID().description
|
||||||
|
|
||||||
|
func unsubscribe() {
|
||||||
|
damus.pool.unsubscribe(sub_id: subscription_uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func subscribe(filters: [NostrFilter]) {
|
||||||
|
damus.pool.register_handler(sub_id: subscription_uuid, handler: handle_event)
|
||||||
|
damus.pool.send(.subscribe(.init(filters: filters, sub_id: subscription_uuid)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||||
|
guard case .nostr_event(let nostr_response) = ev else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard case .event(let id, let nostr_event) = nostr_response else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is current event
|
||||||
|
if id == subscription_uuid {
|
||||||
|
if event != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event = nostr_event
|
||||||
|
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func load() {
|
||||||
|
subscribe(filters: [
|
||||||
|
NostrFilter(
|
||||||
|
ids: [self.event_id],
|
||||||
|
limit: 1
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
if event == nil {
|
||||||
|
ProgressView().padding()
|
||||||
|
} else {
|
||||||
|
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: event!.id)) {
|
||||||
|
EventView(damus: damus, event: event!, show_friend_icon: true, size: .small, embedded: true)
|
||||||
|
}.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(minWidth: 0, maxWidth: .infinity)
|
||||||
|
.border(Color.gray.opacity(0.2), width: 1)
|
||||||
|
.cornerRadius(2)
|
||||||
|
.onAppear {
|
||||||
|
self.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct EventView: View {
|
struct EventView: View {
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let highlight: Highlight
|
let highlight: Highlight
|
||||||
@@ -43,34 +127,42 @@ struct EventView: View {
|
|||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
let show_friend_icon: Bool
|
let show_friend_icon: Bool
|
||||||
|
let size: EventViewKind
|
||||||
|
let embedded: Bool
|
||||||
|
|
||||||
@EnvironmentObject var action_bar: ActionBarModel
|
@EnvironmentObject var action_bar: ActionBarModel
|
||||||
|
|
||||||
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool) {
|
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
|
||||||
self.event = event
|
self.event = event
|
||||||
self.highlight = highlight
|
self.highlight = highlight
|
||||||
self.has_action_bar = has_action_bar
|
self.has_action_bar = has_action_bar
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.pubkey = event.pubkey
|
self.pubkey = event.pubkey
|
||||||
self.show_friend_icon = show_friend_icon
|
self.show_friend_icon = show_friend_icon
|
||||||
|
self.size = size
|
||||||
|
self.embedded = embedded
|
||||||
}
|
}
|
||||||
|
|
||||||
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool) {
|
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
|
||||||
self.event = event
|
self.event = event
|
||||||
self.highlight = .none
|
self.highlight = .none
|
||||||
self.has_action_bar = false
|
self.has_action_bar = false
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.pubkey = event.pubkey
|
self.pubkey = event.pubkey
|
||||||
self.show_friend_icon = show_friend_icon
|
self.show_friend_icon = show_friend_icon
|
||||||
|
self.size = size
|
||||||
|
self.embedded = embedded
|
||||||
}
|
}
|
||||||
|
|
||||||
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool) {
|
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
|
||||||
self.event = event
|
self.event = event
|
||||||
self.highlight = .none
|
self.highlight = .none
|
||||||
self.has_action_bar = false
|
self.has_action_bar = false
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.show_friend_icon = show_friend_icon
|
self.show_friend_icon = show_friend_icon
|
||||||
|
self.size = size
|
||||||
|
self.embedded = embedded
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -108,25 +200,44 @@ struct EventView: View {
|
|||||||
|
|
||||||
func TextEvent(_ event: NostrEvent, pubkey: String) -> some View {
|
func TextEvent(_ event: NostrEvent, pubkey: String) -> some View {
|
||||||
let content = event.get_content(damus.keypair.privkey)
|
let content = event.get_content(damus.keypair.privkey)
|
||||||
|
|
||||||
return HStack(alignment: .top) {
|
return HStack(alignment: .top) {
|
||||||
let profile = damus.profiles.lookup(id: pubkey)
|
let profile = damus.profiles.lookup(id: pubkey)
|
||||||
VStack {
|
|
||||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
if size != .selected {
|
||||||
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
VStack {
|
||||||
|
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
||||||
NavigationLink(destination: pv) {
|
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
||||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
|
|
||||||
|
if !embedded {
|
||||||
|
NavigationLink(destination: pv) {
|
||||||
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon)
|
if size == .selected {
|
||||||
Text("\(format_relative_time(event.created_at))")
|
VStack {
|
||||||
.font(.body)
|
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
||||||
.foregroundColor(.gray)
|
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
||||||
|
|
||||||
|
NavigationLink(destination: pv) {
|
||||||
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon, size: size)
|
||||||
|
if size != .selected {
|
||||||
|
Text("\(format_relative_time(event.created_at))")
|
||||||
|
.font(eventviewsize_to_font(size))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.is_reply(damus.keypair.privkey) {
|
if event.is_reply(damus.keypair.privkey) {
|
||||||
@@ -136,16 +247,55 @@ struct EventView: View {
|
|||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_images(contacts: damus.contacts, ev: event), artifacts: .just_content(content))
|
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_images(contacts: damus.contacts, ev: event), artifacts: .just_content(content), size: self.size)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.allowsHitTesting(!embedded)
|
||||||
if has_action_bar {
|
|
||||||
let bar = make_actionbar_model(ev: event, damus: damus)
|
if !embedded {
|
||||||
EventActionBar(damus_state: damus, event: event, bar: bar)
|
let blocks = event.blocks(damus.keypair.privkey).filter { block in
|
||||||
|
guard case .mention(let mention) = block else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard case .event = mention.type else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if mention.ref.key != "e" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MARK: - Preview
|
||||||
|
if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" {
|
||||||
|
BuilderEventView(damus: damus, event_id: mention.ref.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
if !embedded {
|
||||||
.padding([.top], 4)
|
if has_action_bar {
|
||||||
|
if size == .selected {
|
||||||
|
Text("\(format_date(event.created_at))")
|
||||||
|
.padding(.top, 10)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding([.bottom], 4)
|
||||||
|
} else {
|
||||||
|
Rectangle().frame(height: 2).opacity(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let bar = make_actionbar_model(ev: event, damus: damus)
|
||||||
|
EventActionBar(damus_state: damus, event: event, bar: bar)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding([.top], 4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding([.leading], 2)
|
.padding([.leading], 2)
|
||||||
}
|
}
|
||||||
@@ -231,6 +381,15 @@ func format_relative_time(_ created_at: Int64) -> String
|
|||||||
return time_ago_since(Date(timeIntervalSince1970: Double(created_at)))
|
return time_ago_since(Date(timeIntervalSince1970: Double(created_at)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func format_date(_ created_at: Int64) -> String {
|
||||||
|
let date = Date(timeIntervalSince1970: TimeInterval(created_at))
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.timeStyle = .short
|
||||||
|
dateFormatter.dateStyle = .short
|
||||||
|
return dateFormatter.string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
|
func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
|
||||||
let desc = make_reply_description(event.tags)
|
let desc = make_reply_description(event.tags)
|
||||||
let pubkeys = desc.pubkeys
|
let pubkeys = desc.pubkeys
|
||||||
@@ -285,6 +444,23 @@ func make_actionbar_model(ev: NostrEvent, damus: DamusState) -> ActionBarModel {
|
|||||||
|
|
||||||
struct EventView_Previews: PreviewProvider {
|
struct EventView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true)
|
VStack {
|
||||||
|
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .small)
|
||||||
|
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .normal)
|
||||||
|
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .big)
|
||||||
|
|
||||||
|
EventView(
|
||||||
|
event: NostrEvent(
|
||||||
|
content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool",
|
||||||
|
pubkey: "pk",
|
||||||
|
createdAt: Int64(Date().timeIntervalSince1970 - 100)
|
||||||
|
),
|
||||||
|
highlight: .none,
|
||||||
|
has_action_bar: true,
|
||||||
|
damus: test_damus_state(),
|
||||||
|
show_friend_icon: true,
|
||||||
|
size: .selected
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ struct MentionView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
switch mention.type {
|
switch mention.type {
|
||||||
case .pubkey:
|
case .pubkey:
|
||||||
PubkeyView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id)
|
let pk = bech32_pubkey(mention.ref.ref_id) ?? mention.ref.ref_id
|
||||||
|
PubkeyView(pubkey: pk, relay: mention.ref.relay_id)
|
||||||
case .event:
|
case .event:
|
||||||
Text("< e >")
|
Text("< e >")
|
||||||
//EventBlockView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id)
|
//EventBlockView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id)
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ struct NoteContentView: View {
|
|||||||
@State var artifacts: NoteArtifacts
|
@State var artifacts: NoteArtifacts
|
||||||
|
|
||||||
@State var metaData: [LPLinkMetadata] = []
|
@State var metaData: [LPLinkMetadata] = []
|
||||||
|
let size: EventViewKind
|
||||||
|
|
||||||
func MainContent() -> some View {
|
func MainContent() -> some View {
|
||||||
let md_opts: AttributedString.MarkdownParsingOptions =
|
let md_opts: AttributedString.MarkdownParsingOptions =
|
||||||
@@ -75,10 +76,10 @@ struct NoteContentView: View {
|
|||||||
return VStack(alignment: .leading) {
|
return VStack(alignment: .leading) {
|
||||||
if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) {
|
if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) {
|
||||||
Text(txt)
|
Text(txt)
|
||||||
.font(.body)
|
.font(eventviewsize_to_font(size))
|
||||||
} else {
|
} else {
|
||||||
Text(artifacts.content)
|
Text(artifacts.content)
|
||||||
.font(.body)
|
.font(eventviewsize_to_font(size))
|
||||||
}
|
}
|
||||||
if show_images && artifacts.images.count > 0 {
|
if show_images && artifacts.images.count > 0 {
|
||||||
ImageCarousel(urls: artifacts.images)
|
ImageCarousel(urls: artifacts.images)
|
||||||
@@ -150,8 +151,8 @@ func mention_str(_ m: Mention, profiles: Profiles) -> String {
|
|||||||
let disp = Profile.displayName(profile: profile, pubkey: pk)
|
let disp = Profile.displayName(profile: profile, pubkey: pk)
|
||||||
return "[@\(disp)](nostr:\(encode_pubkey_uri(m.ref)))"
|
return "[@\(disp)](nostr:\(encode_pubkey_uri(m.ref)))"
|
||||||
case .event:
|
case .event:
|
||||||
let evid = m.ref.ref_id
|
let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id
|
||||||
return "[&\(abbrev_pubkey(evid))](nostr:\(encode_event_id_uri(m.ref)))"
|
return "[@\(abbrev_pubkey(bevid))](nostr:\(encode_event_id_uri(m.ref)))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +162,6 @@ struct NoteContentView_Previews: PreviewProvider {
|
|||||||
let state = test_damus_state()
|
let state = test_damus_state()
|
||||||
let content = "hi there https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
|
let content = "hi there https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
|
||||||
let artifacts = NoteArtifacts(content: content, images: [], invoices: [], links: [])
|
let artifacts = NoteArtifacts(content: content, images: [], invoices: [], links: [])
|
||||||
NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts)
|
NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts, size: .normal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ struct ProfileName: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.fontWeight(prefix == "@" ? .none : .bold)
|
.fontWeight(prefix == "@" ? .none : .bold)
|
||||||
@@ -104,20 +103,24 @@ struct EventProfileName: View {
|
|||||||
|
|
||||||
@State var display_name: String?
|
@State var display_name: String?
|
||||||
|
|
||||||
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool) {
|
let size: EventViewKind
|
||||||
|
|
||||||
|
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.prefix = ""
|
self.prefix = ""
|
||||||
self.contacts = contacts
|
self.contacts = contacts
|
||||||
self.show_friend_confirmed = show_friend_confirmed
|
self.show_friend_confirmed = show_friend_confirmed
|
||||||
|
self.size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool) {
|
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.contacts = contacts
|
self.contacts = contacts
|
||||||
self.show_friend_confirmed = show_friend_confirmed
|
self.show_friend_confirmed = show_friend_confirmed
|
||||||
|
self.size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
var friend_icon: String? {
|
var friend_icon: String? {
|
||||||
@@ -144,10 +147,10 @@ struct EventProfileName: View {
|
|||||||
|
|
||||||
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.font(.body)
|
.font(eventviewsize_to_font(size))
|
||||||
} else {
|
} else {
|
||||||
Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||||
.font(.body)
|
.font(eventviewsize_to_font(size))
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,12 +161,12 @@ struct ProfileView: View {
|
|||||||
let data = damus_state.profiles.lookup(id: profile.pubkey)
|
let data = damus_state.profiles.lookup(id: profile.pubkey)
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .custom(Color.black, 2), profiles: damus_state.profiles)
|
ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
is_zoomed.toggle()
|
is_zoomed.toggle()
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $is_zoomed) {
|
.sheet(isPresented: $is_zoomed) {
|
||||||
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .custom(Color.black, 2), profiles: damus_state.profiles)
|
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .none, profiles: damus_state.profiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ struct ReplyQuoteView: View {
|
|||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteContentView(privkey: privkey, event: event, profiles: profiles, show_images: false, artifacts: .just_content(event.content))
|
NoteContentView(privkey: privkey, event: event, profiles: profiles, show_images: false, artifacts: .just_content(event.content), size: .normal)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,11 @@ struct SearchResultsView: View {
|
|||||||
let prof_model = ProfileModel(pubkey: h, damus: damus_state)
|
let prof_model = ProfileModel(pubkey: h, damus: damus_state)
|
||||||
let f = FollowersModel(damus_state: damus_state, target: h)
|
let f = FollowersModel(damus_state: damus_state, target: h)
|
||||||
let prof_view = ProfileView(damus_state: damus_state, profile: prof_model, followers: f)
|
let prof_view = ProfileView(damus_state: damus_state, profile: prof_model, followers: f)
|
||||||
let thread_model = ThreadModel(evid: h, damus_state: damus_state)
|
let ev_view = BuildThreadV2View(
|
||||||
let ev_view = ThreadView(thread: thread_model, damus: damus_state, is_chatroom: false)
|
damus: damus_state,
|
||||||
|
event_id: h
|
||||||
|
)
|
||||||
|
|
||||||
VStack(spacing: 50) {
|
VStack(spacing: 50) {
|
||||||
NavigationLink(destination: prof_view) {
|
NavigationLink(destination: prof_view) {
|
||||||
Text("Goto profile \(h)")
|
Text("Goto profile \(h)")
|
||||||
@@ -66,8 +69,10 @@ struct SearchResultsView: View {
|
|||||||
case .note(let nid):
|
case .note(let nid):
|
||||||
let decoded = try? bech32_decode(nid)
|
let decoded = try? bech32_decode(nid)
|
||||||
let hex = hex_encode(decoded!.data)
|
let hex = hex_encode(decoded!.data)
|
||||||
let thread_model = ThreadModel(evid: hex, damus_state: damus_state)
|
let ev_view = BuildThreadV2View(
|
||||||
let ev_view = ThreadView(thread: thread_model, damus: damus_state, is_chatroom: false)
|
damus: damus_state,
|
||||||
|
event_id: hex
|
||||||
|
)
|
||||||
NavigationLink(destination: ev_view) {
|
NavigationLink(destination: ev_view) {
|
||||||
Text("Goto post \(nid)")
|
Text("Goto post \(nid)")
|
||||||
}
|
}
|
||||||
|
|||||||
314
damus/Views/ThreadV2View.swift
Normal file
314
damus/Views/ThreadV2View.swift
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
//
|
||||||
|
// ThreadV2View.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Thomas Tastet on 25/12/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ThreadV2 {
|
||||||
|
var parentEvents: [NostrEvent]
|
||||||
|
var current: NostrEvent
|
||||||
|
var childEvents: [NostrEvent]
|
||||||
|
|
||||||
|
mutating func clean() {
|
||||||
|
// remove duplicates
|
||||||
|
self.parentEvents = Array(Set(self.parentEvents))
|
||||||
|
self.childEvents = Array(Set(self.childEvents))
|
||||||
|
|
||||||
|
// remove empty contents
|
||||||
|
self.parentEvents = self.parentEvents.filter { event in
|
||||||
|
return !event.content.isEmpty
|
||||||
|
}
|
||||||
|
self.childEvents = self.childEvents.filter { event in
|
||||||
|
return !event.content.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort events by publication date
|
||||||
|
self.parentEvents = self.parentEvents.sorted { event1, event2 in
|
||||||
|
return event1 < event2
|
||||||
|
}
|
||||||
|
self.childEvents = self.childEvents.sorted { event1, event2 in
|
||||||
|
return event1 < event2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct BuildThreadV2View: View {
|
||||||
|
let damus: DamusState
|
||||||
|
|
||||||
|
@State var parents_ids: [String] = []
|
||||||
|
let event_id: String
|
||||||
|
|
||||||
|
@State var current_event: NostrEvent? = nil
|
||||||
|
|
||||||
|
@State var thread: ThreadV2? = nil
|
||||||
|
|
||||||
|
@State var current_events_uuid: String = ""
|
||||||
|
@State var childs_events_uuid: String = ""
|
||||||
|
@State var parents_events_uuids: [String] = []
|
||||||
|
|
||||||
|
@State var subscriptions_uuids: [String] = []
|
||||||
|
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
init(damus: DamusState, event_id: String) {
|
||||||
|
self.damus = damus
|
||||||
|
self.event_id = event_id
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe_all() {
|
||||||
|
print("ThreadV2View: Unsubscribe all..")
|
||||||
|
|
||||||
|
for subscriptions in subscriptions_uuids {
|
||||||
|
unsubscribe(subscriptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe(_ sub_id: String) {
|
||||||
|
if subscriptions_uuids.contains(sub_id) {
|
||||||
|
damus.pool.unsubscribe(sub_id: sub_id)
|
||||||
|
|
||||||
|
subscriptions_uuids.remove(at: subscriptions_uuids.firstIndex(of: sub_id)!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func subscribe(filters: [NostrFilter], sub_id: String = UUID().description) -> String {
|
||||||
|
damus.pool.register_handler(sub_id: sub_id, handler: handle_event)
|
||||||
|
damus.pool.send(.subscribe(.init(filters: filters, sub_id: sub_id)))
|
||||||
|
|
||||||
|
subscriptions_uuids.append(sub_id)
|
||||||
|
|
||||||
|
return sub_id
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||||
|
guard case .nostr_event(let nostr_response) = ev else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard case .event(let id, let nostr_event) = nostr_response else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is current event
|
||||||
|
if id == current_events_uuid {
|
||||||
|
if current_event != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
current_event = nostr_event
|
||||||
|
|
||||||
|
thread = ThreadV2(
|
||||||
|
parentEvents: [],
|
||||||
|
current: current_event!,
|
||||||
|
childEvents: []
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get parents
|
||||||
|
parents_ids = current_event!.tags.enumerated().filter { (index, tag) in
|
||||||
|
return tag.count >= 2 && tag[0] == "e" && !current_event!.content.contains("#[\(index)]")
|
||||||
|
}.map { tag in
|
||||||
|
return tag.1[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
print("ThreadV2View: Parents list: (\(parents_ids)")
|
||||||
|
|
||||||
|
if parents_ids.count > 0 {
|
||||||
|
// Ask for parents
|
||||||
|
let parents_events = NostrFilter(
|
||||||
|
ids: parents_ids,
|
||||||
|
limit: UInt32(parents_ids.count)
|
||||||
|
)
|
||||||
|
|
||||||
|
let uuid = subscribe(filters: [parents_events])
|
||||||
|
parents_events_uuids.append(uuid)
|
||||||
|
print("ThreadV2View: Ask for parents (\(uuid)) (\(parents_events))")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask for children
|
||||||
|
let childs_events = NostrFilter(
|
||||||
|
referenced_ids: [self.event_id],
|
||||||
|
limit: 50
|
||||||
|
)
|
||||||
|
childs_events_uuid = subscribe(filters: [childs_events])
|
||||||
|
print("ThreadV2View: Ask for children (\(childs_events) (\(childs_events_uuid))")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if parents_events_uuids.contains(id) {
|
||||||
|
// We are filtering this later
|
||||||
|
thread!.parentEvents.append(nostr_event)
|
||||||
|
|
||||||
|
// Get parents of parents
|
||||||
|
let local_parents_ids = nostr_event.tags.enumerated().filter { (index, tag) in
|
||||||
|
return tag.count >= 2 && tag[0] == "e" && !nostr_event.content.contains("#[\(index)]")
|
||||||
|
}.map { tag in
|
||||||
|
return tag.1[1]
|
||||||
|
}.filter { tag_id in
|
||||||
|
return !parents_ids.contains(tag_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
print("ThreadV2View: Sub Parents list: (\(local_parents_ids))")
|
||||||
|
|
||||||
|
// Expand new parents id
|
||||||
|
parents_ids.append(contentsOf: local_parents_ids)
|
||||||
|
|
||||||
|
if local_parents_ids.count > 0 {
|
||||||
|
// Ask for parents
|
||||||
|
let parents_events = NostrFilter(
|
||||||
|
ids: local_parents_ids,
|
||||||
|
limit: UInt32(local_parents_ids.count)
|
||||||
|
)
|
||||||
|
let uuid = subscribe(filters: [parents_events])
|
||||||
|
parents_events_uuids.append(uuid)
|
||||||
|
print("ThreadV2View: Ask for sub_parents (\(local_parents_ids)) \(uuid)")
|
||||||
|
}
|
||||||
|
|
||||||
|
thread!.clean()
|
||||||
|
unsubscribe(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if id == childs_events_uuid {
|
||||||
|
// We are filtering this later
|
||||||
|
thread!.childEvents.append(nostr_event)
|
||||||
|
|
||||||
|
thread!.clean()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reload() {
|
||||||
|
self.unsubscribe_all()
|
||||||
|
print("ThreadV2View: Reload!")
|
||||||
|
|
||||||
|
// Get the current event
|
||||||
|
current_events_uuid = subscribe(filters: [
|
||||||
|
NostrFilter(
|
||||||
|
ids: [self.event_id],
|
||||||
|
limit: 1
|
||||||
|
)
|
||||||
|
])
|
||||||
|
print("subscribing to threadV2 \(event_id) with sub_id \(current_events_uuid)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
if thread == nil {
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
ThreadV2View(damus: damus, thread: thread!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if self.thread == nil {
|
||||||
|
self.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
self.unsubscribe_all()
|
||||||
|
}
|
||||||
|
.onReceive(handle_notify(.switched_timeline)) { n in
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadV2View: View {
|
||||||
|
let damus: DamusState
|
||||||
|
let thread: ThreadV2
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollViewReader { reader in
|
||||||
|
ScrollView {
|
||||||
|
VStack {
|
||||||
|
// MARK: - Parents events view
|
||||||
|
VStack {
|
||||||
|
ForEach(thread.parentEvents, id: \.id) { event in
|
||||||
|
NavigationLink(destination: BuildThreadV2View(
|
||||||
|
damus: damus,
|
||||||
|
event_id: event.id
|
||||||
|
)){
|
||||||
|
EventView(
|
||||||
|
event: event,
|
||||||
|
highlight: .none,
|
||||||
|
has_action_bar: true,
|
||||||
|
damus: damus,
|
||||||
|
show_friend_icon: true, // TODO: change it
|
||||||
|
size: .small
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.onAppear {
|
||||||
|
// TODO: find another solution to prevent layout shifting and layout blocking on large responses
|
||||||
|
reader.scrollTo("main", anchor: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.background(GeometryReader { geometry in
|
||||||
|
// get the height and width of the EventView view
|
||||||
|
let eventHeight = geometry.frame(in: .global).height
|
||||||
|
// let eventWidth = geometry.frame(in: .global).width
|
||||||
|
|
||||||
|
// vertical gray line in the background
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.gray.opacity(0.25))
|
||||||
|
.frame(width: 2, height: eventHeight)
|
||||||
|
.offset(x: 25, y: 40)
|
||||||
|
})
|
||||||
|
|
||||||
|
// MARK: - Actual event view
|
||||||
|
EventView(
|
||||||
|
event: thread.current,
|
||||||
|
highlight: .none,
|
||||||
|
has_action_bar: true,
|
||||||
|
damus: damus,
|
||||||
|
show_friend_icon: true, // TODO: change it
|
||||||
|
size: .selected
|
||||||
|
).id("main")
|
||||||
|
|
||||||
|
// MARK: - Responses of the actual event view
|
||||||
|
ForEach(thread.childEvents, id: \.id) { event in
|
||||||
|
NavigationLink(destination: BuildThreadV2View(
|
||||||
|
damus: damus,
|
||||||
|
event_id: event.id
|
||||||
|
)){
|
||||||
|
EventView(
|
||||||
|
event: event,
|
||||||
|
highlight: .none,
|
||||||
|
has_action_bar: true,
|
||||||
|
damus: damus,
|
||||||
|
show_friend_icon: true, // TODO: change it
|
||||||
|
size: .small
|
||||||
|
)
|
||||||
|
}.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.padding().navigationBarTitle("Thread")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadV2View_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
BuildThreadV2View(damus: test_damus_state(), event_id: "ac9fd97b53b0c1d22b3aea2a3d62e11ae393960f5f91ee1791987d60151339a7")
|
||||||
|
ThreadV2View(
|
||||||
|
damus: test_damus_state(),
|
||||||
|
thread: ThreadV2(
|
||||||
|
parentEvents: [
|
||||||
|
NostrEvent(id: "1", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||||
|
NostrEvent(id: "2", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||||
|
NostrEvent(id: "3", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||||
|
],
|
||||||
|
current: NostrEvent(id: "4", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||||
|
childEvents: [
|
||||||
|
NostrEvent(id: "5", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||||
|
NostrEvent(id: "6", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,11 +24,14 @@ struct InnerTimelineView: View {
|
|||||||
EmptyTimelineView()
|
EmptyTimelineView()
|
||||||
} else {
|
} else {
|
||||||
ForEach(events.filter(filter), id: \.id) { (ev: NostrEvent) in
|
ForEach(events.filter(filter), id: \.id) { (ev: NostrEvent) in
|
||||||
let tm = ThreadModel(event: inner_event_or_self(ev: ev), damus_state: damus)
|
//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)
|
||||||
|
|
||||||
NavigationLink(destination: tv) {
|
NavigationLink(destination: BuildThreadV2View(
|
||||||
|
damus: damus,
|
||||||
|
event_id: ev.id
|
||||||
|
)) {
|
||||||
EventView(event: ev, highlight: .none, has_action_bar: true, damus: damus, show_friend_icon: show_friend_icon)
|
EventView(event: ev, highlight: .none, has_action_bar: true, damus: damus, show_friend_icon: show_friend_icon)
|
||||||
}
|
}
|
||||||
.isDetailLink(true)
|
.isDetailLink(true)
|
||||||
|
|||||||
Reference in New Issue
Block a user