diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 3d6a36dc..20881d19 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -364,7 +364,7 @@ 4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE02981A83900D66079 /* MutelistView.swift */; }; 4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE22981BC7D00D66079 /* UserView.swift */; }; 4CF0ABE52981EE0C00D66079 /* EULAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE42981EE0C00D66079 /* EULAView.swift */; }; - 4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE6298444FC00D66079 /* MutedEventView.swift */; }; + 4CF0ABE7298444FD00D66079 /* EventMutingContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE6298444FC00D66079 /* EventMutingContainerView.swift */; }; 4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE829844AF100D66079 /* AnyCodable.swift */; }; 4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */; }; 4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; }; @@ -427,6 +427,8 @@ D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; }; D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; }; D78525252A7B2EA4002FA637 /* NoteContentViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */; }; + D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7870BC02AC4750B0080BA88 /* MentionView.swift */; }; + D7870BC32AC47EBC0080BA88 /* EventLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */; }; D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; }; E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; }; E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; }; @@ -1048,7 +1050,7 @@ 4CF0ABE02981A83900D66079 /* MutelistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutelistView.swift; sourceTree = ""; }; 4CF0ABE22981BC7D00D66079 /* UserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserView.swift; sourceTree = ""; }; 4CF0ABE42981EE0C00D66079 /* EULAView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EULAView.swift; sourceTree = ""; }; - 4CF0ABE6298444FC00D66079 /* MutedEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedEventView.swift; sourceTree = ""; }; + 4CF0ABE6298444FC00D66079 /* EventMutingContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMutingContainerView.swift; sourceTree = ""; }; 4CF0ABE829844AF100D66079 /* AnyCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyCodable.swift; sourceTree = ""; }; 4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = ""; }; 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = ""; }; @@ -1110,6 +1112,8 @@ D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = ""; }; D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = ""; }; D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContentViewTests.swift; sourceTree = ""; }; + D7870BC02AC4750B0080BA88 /* MentionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionView.swift; sourceTree = ""; }; + D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLoaderView.swift; sourceTree = ""; }; D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = ""; }; E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = ""; }; E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = ""; }; @@ -2001,12 +2005,14 @@ 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */, 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */, 4CC7AAF9297F64AC00430951 /* EventMenu.swift */, - 4CF0ABE6298444FC00D66079 /* MutedEventView.swift */, + 4CF0ABE6298444FC00D66079 /* EventMutingContainerView.swift */, 4C3D52B5298DB4E6001C5831 /* ZapEvent.swift */, 4C3D52B7298DB5C6001C5831 /* TextEvent.swift */, 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */, 4CA9275B2A28FF570098A105 /* Longform */, 4CA927602A290E340098A105 /* EventShell.swift */, + D7870BC02AC4750B0080BA88 /* MentionView.swift */, + D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */, ); path = Events; sourceTree = ""; @@ -2558,6 +2564,7 @@ 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */, 3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */, + D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */, 4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */, 4C363AA228296A7E006E126D /* SearchView.swift in Sources */, 4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */, @@ -2639,7 +2646,7 @@ 4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */, 4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */, 4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */, - 4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */, + 4CF0ABE7298444FD00D66079 /* EventMutingContainerView.swift in Sources */, 9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */, 4C1253502A76C5B20004F4B8 /* UnfollowedNotify.swift in Sources */, 4C86F7C62A76C51100EC0817 /* AttachedWalletNotify.swift in Sources */, @@ -2689,6 +2696,7 @@ 4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */, 4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */, 4C3EA64928FF597700C48A62 /* bech32.c in Sources */, + D7870BC32AC47EBC0080BA88 /* EventLoaderView.swift in Sources */, 4CE879522996B68900F758CC /* RelayType.swift in Sources */, 4CE8795B2996C47A00F758CC /* ZapsModel.swift in Sources */, 4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */, diff --git a/damus/Views/Events/BuilderEventView.swift b/damus/Views/Events/BuilderEventView.swift index 1e798598..96adb55d 100644 --- a/damus/Views/Events/BuilderEventView.swift +++ b/damus/Views/Events/BuilderEventView.swift @@ -10,71 +10,38 @@ import SwiftUI struct BuilderEventView: View { let damus: DamusState let event_id: NoteId - @State var event: NostrEvent? - @State var subscription_uuid: String = UUID().description + let event: NostrEvent? init(damus: DamusState, event: NostrEvent) { - _event = State(initialValue: event) + self.event = event self.damus = damus self.event_id = event.id } init(damus: DamusState, event_id: NoteId) { - let event = damus.events.lookup(event_id) self.event_id = event_id self.damus = damus - _event = State(initialValue: event) + self.event = nil } - 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 - } - - guard id == subscription_uuid else { - return - } - - if event != nil { - return - } - - event = nostr_event - - unsubscribe() - } - - func load() { - subscribe(filters: [ - NostrFilter(ids: [self.event_id], limit: 1) - ]) + func Event(event: NostrEvent) -> some View { + return EventView(damus: damus, event: event, options: .embedded) + .padding([.top, .bottom], 8) + .onTapGesture { + let ev = event.get_inner_event(cache: damus.events) ?? event + let thread = ThreadModel(event: ev, damus_state: damus) + damus.nav.push(route: .Thread(thread: thread)) + } } var body: some View { VStack { if let event { - EventView(damus: damus, event: event, options: .embedded) - .padding([.top, .bottom], 8) - .onTapGesture { - let ev = event.get_inner_event(cache: damus.events) ?? event - let thread = ThreadModel(event: ev, damus_state: damus) - damus.nav.push(route: .Thread(thread: thread)) - } + self.Event(event: event) } else { - ProgressView().padding() + EventLoaderView(damus_state: damus, event_id: self.event_id) { loaded_event in + self.Event(event: loaded_event) + } } } .frame(minWidth: 0, maxWidth: .infinity) @@ -82,12 +49,6 @@ struct BuilderEventView: View { RoundedRectangle(cornerRadius: 10) .stroke(Color.gray.opacity(0.2), lineWidth: 1.0) ) - .onAppear { - guard event == nil else { - return - } - self.load() - } } } diff --git a/damus/Views/Events/EventLoaderView.swift b/damus/Views/Events/EventLoaderView.swift new file mode 100644 index 00000000..c41d65e0 --- /dev/null +++ b/damus/Views/Events/EventLoaderView.swift @@ -0,0 +1,87 @@ +// +// EventLoaderView.swift +// damus +// +// Created by Daniel D’Aquino on 2023-09-27. +// + +import SwiftUI + +/// This view handles the loading logic for Nostr events, so that you can easily use views that require `NostrEvent`, even if you only have a `NoteId` +struct EventLoaderView: View { + let damus_state: DamusState + let event_id: NoteId + @State var event: NostrEvent? + @State var subscription_uuid: String = UUID().description + let content: (NostrEvent) -> Content + + init(damus_state: DamusState, event_id: NoteId, @ViewBuilder content: @escaping (NostrEvent) -> Content) { + self.damus_state = damus_state + self.event_id = event_id + self.content = content + let event = damus_state.events.lookup(event_id) + _event = State(initialValue: event) + } + + func unsubscribe() { + damus_state.pool.unsubscribe(sub_id: subscription_uuid) + } + + func subscribe(filters: [NostrFilter]) { + damus_state.pool.register_handler(sub_id: subscription_uuid, handler: handle_event) + damus_state.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 + } + + guard id == subscription_uuid else { + return + } + + 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 let event { + self.content(event) + } else { + ProgressView().padding() + } + } + .onAppear { + guard event == nil else { + return + } + self.load() + } + } +} + + +struct EventLoaderView_Previews: PreviewProvider { + static var previews: some View { + EventLoaderView(damus_state: test_damus_state, event_id: test_note.id) { event in + EventView(damus: test_damus_state, event: event) + } + } +} diff --git a/damus/Views/Events/MutedEventView.swift b/damus/Views/Events/EventMutingContainerView.swift similarity index 58% rename from damus/Views/Events/MutedEventView.swift rename to damus/Views/Events/EventMutingContainerView.swift index 42d7c463..e8b7cb41 100644 --- a/damus/Views/Events/MutedEventView.swift +++ b/damus/Views/Events/EventMutingContainerView.swift @@ -1,5 +1,5 @@ // -// MutedEventView.swift +// EventMutingContainerView.swift // damus // // Created by William Casarin on 2023-01-27. @@ -7,57 +7,45 @@ import SwiftUI -struct MutedEventView: View { +/// A container view that shows or hides provided content based on whether the given event should be muted or not, with built-in user controls to show or hide content, and an option to customize the muted box +struct EventMutingContainerView: View { + typealias MuteBoxViewClosure = ((_ shown: Binding) -> AnyView) + let damus_state: DamusState let event: NostrEvent + let content: Content + var customMuteBox: MuteBoxViewClosure? - let selected: Bool @State var shown: Bool - init(damus_state: DamusState, event: NostrEvent, selected: Bool) { + init(damus_state: DamusState, event: NostrEvent, @ViewBuilder content: () -> Content) { self.damus_state = damus_state self.event = event - self.selected = selected + self.content = content() self._shown = State(initialValue: should_show_event(keypair: damus_state.keypair, hellthreads: damus_state.muted_threads, contacts: damus_state.contacts, ev: event)) } + init(damus_state: DamusState, event: NostrEvent, muteBox: @escaping MuteBoxViewClosure, @ViewBuilder content: () -> Content) { + self.init(damus_state: damus_state, event: event, content: content) + self.customMuteBox = muteBox + } + var should_mute: Bool { return !should_show_event(keypair: damus_state.keypair, hellthreads: damus_state.muted_threads, contacts: damus_state.contacts, ev: event) } - var MutedBox: some View { - ZStack { - RoundedRectangle(cornerRadius: 20) - .foregroundColor(DamusColors.adaptableGrey) - - HStack { - Text("Note from a user you've muted", comment: "Text to indicate that what is being shown is a note from a user who has been muted.") - Spacer() - Button(shown ? NSLocalizedString("Hide", comment: "Button to hide a note from a user who has been muted.") : NSLocalizedString("Show", comment: "Button to show a note from a user who has been muted.")) { - shown.toggle() - } - } - .padding(10) - } - } - - var Event: some View { - Group { - if selected { - SelectedEventView(damus: damus_state, event: event, size: .selected) - } else { - EventView(damus: damus_state, event: event) - } - } - } - var body: some View { Group { if should_mute { - MutedBox + if let customMuteBox { + customMuteBox($shown) + } + else { + EventMutedBoxView(shown: $shown) + } } if shown { - Event + self.content } } .onReceive(handle_notify(.new_mutes)) { mutes in @@ -73,11 +61,34 @@ struct MutedEventView: View { } } +/// A box that instructs the user about a content that has been muted. +struct EventMutedBoxView: View { + @Binding var shown: Bool + + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: 20) + .foregroundColor(DamusColors.adaptableGrey) + + HStack { + Text("Note from a user you've muted", comment: "Text to indicate that what is being shown is a note from a user who has been muted.") + Spacer() + Button(shown ? NSLocalizedString("Hide", comment: "Button to hide a note from a user who has been muted.") : NSLocalizedString("Show", comment: "Button to show a note from a user who has been muted.")) { + shown.toggle() + } + } + .padding(10) + } + } +} + struct MutedEventView_Previews: PreviewProvider { static var previews: some View { - MutedEventView(damus_state: test_damus_state, event: test_note, selected: false) + EventMutingContainerView(damus_state: test_damus_state, event: test_note) { + EventView(damus: test_damus_state, event: test_note) + } .frame(width: .infinity, height: 50) } } diff --git a/damus/Views/Events/EventShell.swift b/damus/Views/Events/EventShell.swift index 5899b45c..3aab1dca 100644 --- a/damus/Views/Events/EventShell.swift +++ b/damus/Views/Events/EventShell.swift @@ -42,10 +42,6 @@ struct EventShell: View { return first_eref_mention(ev: event, keypair: state.keypair) } - - func Mention(_ mention: Mention) -> some View { - return BuilderEventView(damus: state, event_id: mention.ref) - } var ActionBar: some View { return EventActionBar(damus_state: state, event: event) @@ -78,7 +74,7 @@ struct EventShell: View { content if let mention = get_mention() { - Mention(mention) + MentionView(damus_state: state, mention: mention) } if has_action_bar { @@ -107,7 +103,7 @@ struct EventShell: View { content if !options.contains(.no_mentions), let mention = get_mention() { - Mention(mention) + MentionView(damus_state: state, mention: mention) .padding(.horizontal) } @@ -128,7 +124,6 @@ struct EventShell: View { } .contentShape(Rectangle()) .id(event.id) - .frame(maxWidth: .infinity, minHeight: PFP_SIZE) .padding([.bottom], 2) } } diff --git a/damus/Views/Events/MentionView.swift b/damus/Views/Events/MentionView.swift new file mode 100644 index 00000000..a0f43109 --- /dev/null +++ b/damus/Views/Events/MentionView.swift @@ -0,0 +1,32 @@ +// +// MentionView.swift +// damus +// +// Created by Daniel D’Aquino on 2023-09-27. +// + +import SwiftUI + +struct MentionView: View { + let damus_state: DamusState + let mention: Mention + + init(damus_state: DamusState, mention: Mention) { + self.damus_state = damus_state + self.mention = mention + } + + var body: some View { + EventLoaderView(damus_state: damus_state, event_id: mention.ref) { event in + EventMutingContainerView(damus_state: damus_state, event: event) { + BuilderEventView(damus: damus_state, event_id: mention.ref) + } + } + } +} + +struct MentionView_Previews: PreviewProvider { + static var previews: some View { + MentionView(damus_state: test_damus_state, mention: .note(test_note.id)) + } +} diff --git a/damus/Views/Events/SelectedEventView.swift b/damus/Views/Events/SelectedEventView.swift index 60e1c70a..832c92b7 100644 --- a/damus/Views/Events/SelectedEventView.swift +++ b/damus/Views/Events/SelectedEventView.swift @@ -55,10 +55,7 @@ struct SelectedEventView: View { EventBody(damus_state: damus, event: event, size: size, options: [.wide]) - if let mention = first_eref_mention(ev: event, keypair: damus.keypair) { - BuilderEventView(damus: damus, event_id: mention.ref) - .padding(.horizontal) - } + Mention Text(verbatim: "\(format_date(event.created_at))") .padding([.top, .leading, .trailing]) @@ -88,6 +85,15 @@ struct SelectedEventView: View { .compositingGroup() } } + + var Mention: some View { + Group { + if let mention = first_eref_mention(ev: event, keypair: damus.keypair) { + MentionView(damus_state: damus, mention: mention) + .padding(.horizontal) + } + } + } } struct SelectedEventView_Previews: PreviewProvider { diff --git a/damus/Views/Reposts/RepostedEvent.swift b/damus/Views/Reposts/RepostedEvent.swift index 3ac8b4f0..207ee295 100644 --- a/damus/Views/Reposts/RepostedEvent.swift +++ b/damus/Views/Reposts/RepostedEvent.swift @@ -22,7 +22,17 @@ struct RepostedEvent: View { .buttonStyle(PlainButtonStyle()) //SelectedEventView(damus: damus, event: inner_ev, size: .normal) - EventView(damus: damus, event: inner_ev, pubkey: inner_ev.pubkey, options: options.union(.wide)) + EventMutingContainerView( + damus_state: damus, + event: inner_ev, + muteBox: { event_shown in + AnyView( + EventMutedBoxView(shown: event_shown) + .padding(.horizontal, 5) // Add a bit of horizontal padding to avoid the mute box from touching the edges of the screen + ) + }) { + EventView(damus: damus, event: inner_ev, pubkey: inner_ev.pubkey, options: options.union(.wide)) + } } } } diff --git a/damus/Views/ThreadView.swift b/damus/Views/ThreadView.swift index 7789176f..7ef8a315 100644 --- a/damus/Views/ThreadView.swift +++ b/damus/Views/ThreadView.swift @@ -41,10 +41,9 @@ struct ThreadView: View { LazyVStack { // MARK: - Parents events view ForEach(parent_events, id: \.id) { parent_event in - - MutedEventView(damus_state: state, - event: parent_event, - selected: false) + EventMutingContainerView(damus_state: state, event: parent_event) { + EventView(damus: state, event: parent_event) + } .padding(.horizontal) .onTapGesture { thread.set_active_event(parent_event, keypair: self.state.keypair) @@ -68,11 +67,18 @@ struct ThreadView: View { }) // MARK: - Actual event view - MutedEventView( + EventMutingContainerView( damus_state: state, event: self.thread.event, - selected: true - ) + muteBox: { event_shown in + AnyView( + EventMutedBoxView(shown: event_shown) + .padding(5) + ) + } + ) { + SelectedEventView(damus: state, event: self.thread.event, size: .selected) + } .id(self.thread.event.id) /* @@ -83,11 +89,12 @@ struct ThreadView: View { */ ForEach(sorted_child_events, id: \.id) { child_event in - MutedEventView( + EventMutingContainerView( damus_state: state, - event: child_event, - selected: false - ) + event: child_event + ) { + EventView(damus: state, event: child_event) + } .padding(.horizontal) .onTapGesture { thread.set_active_event(child_event, keypair: state.keypair)