From 9d97886e3fb322b03c481d1f8bb6f94e9c6adf20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Sat, 17 Aug 2024 14:24:48 -0700 Subject: [PATCH 01/10] Add convenience functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a convenience initializer for DamusState that is simpler than the normal initializer, to allow extensions to more easily use it. It also includes a new convenience function for `should_blur_images` Signed-off-by: Daniel D’Aquino --- damus/Models/DamusState.swift | 73 +++++++++++++++++++++++++++++++++++ damus/Views/EventView.swift | 10 +++++ 2 files changed, 83 insertions(+) diff --git a/damus/Models/DamusState.swift b/damus/Models/DamusState.swift index 176eb05d..aee37f05 100644 --- a/damus/Models/DamusState.swift +++ b/damus/Models/DamusState.swift @@ -74,6 +74,79 @@ class DamusState: HeadlessDamusState { self.push_notification_client = PushNotificationClient(keypair: keypair, settings: settings) self.emoji_provider = emoji_provider } + + @MainActor + convenience init?(keypair: Keypair) { + // nostrdb + var mndb = Ndb() + if mndb == nil { + // try recovery + print("DB ISSUE! RECOVERING") + mndb = Ndb.safemode() + + // out of space or something?? maybe we need a in-memory fallback + if mndb == nil { + logout(nil) + return nil + } + } + + let navigationCoordinator: NavigationCoordinator = NavigationCoordinator() + let home: HomeModel = HomeModel() + let sub_id = UUID().uuidString + + guard let ndb = mndb else { return nil } + let pubkey = keypair.pubkey + + let pool = RelayPool(ndb: ndb, keypair: keypair) + let model_cache = RelayModelCache() + let relay_filters = RelayFilters(our_pubkey: pubkey) + let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey) + + let settings = UserSettingsStore.globally_load_for(pubkey: pubkey) + + let new_relay_filters = load_relay_filters(pubkey) == nil + for relay in bootstrap_relays { + let descriptor = RelayDescriptor(url: relay, info: .rw) + add_new_relay(model_cache: model_cache, relay_filters: relay_filters, pool: pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: settings.developer_mode) + } + + pool.register_handler(sub_id: sub_id, handler: home.handle_event) + + if let nwc_str = settings.nostr_wallet_connect, + let nwc = WalletConnectURL(str: nwc_str) { + try? pool.add_relay(.nwc(url: nwc.relay)) + } + self.init( + pool: pool, + keypair: keypair, + likes: EventCounter(our_pubkey: pubkey), + boosts: EventCounter(our_pubkey: pubkey), + contacts: Contacts(our_pubkey: pubkey), + mutelist_manager: MutelistManager(user_keypair: keypair), + profiles: Profiles(ndb: ndb), + dms: home.dms, + previews: PreviewCache(), + zaps: Zaps(our_pubkey: pubkey), + lnurls: LNUrls(), + settings: settings, + relay_filters: relay_filters, + relay_model_cache: model_cache, + drafts: Drafts(), + events: EventCache(ndb: ndb), + bookmarks: BookmarksManager(pubkey: pubkey), + postbox: PostBox(pool: pool), + bootstrap_relays: bootstrap_relays, + replies: ReplyCounter(our_pubkey: pubkey), + wallet: WalletModel(settings: settings), + nav: navigationCoordinator, + music: MusicController(onChange: { _ in }), + video: VideoController(), + ndb: ndb, + quote_reposts: .init(our_pubkey: pubkey), + emoji_provider: DefaultEmojiProvider(showAllVariations: true) + ) + } @discardableResult func add_zap(zap: Zapping) -> Bool { diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift index 72c9c543..85de8d90 100644 --- a/damus/Views/EventView.swift +++ b/damus/Views/EventView.swift @@ -73,6 +73,16 @@ func should_blur_images(settings: UserSettingsStore, contacts: Contacts, ev: Nos return true } +// blame the porn bots for this code too +func should_blur_images(damus_state: DamusState, ev: NostrEvent) -> Bool { + return should_blur_images( + settings: damus_state.settings, + contacts: damus_state.contacts, + ev: ev, + our_pubkey: damus_state.pubkey + ) +} + func format_relative_time(_ created_at: UInt32) -> String { return time_ago_since(Date(timeIntervalSince1970: Double(created_at))) From 4f881a56679b13c9a2662ab1792930bab0eb7473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Sat, 17 Aug 2024 14:38:23 -0700 Subject: [PATCH 02/10] Simplify SelectableText state management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit simplifies the state management and information flow for SelectableText. This also fixes issues and inconsistencies with the selected text for a highlight action, which often appeared in some scenarios with the symptom of a highlight action showing the incorrect or outdated selected text. Previously, the state of the selected text and highlight action was tracked in two independent state/binding variables which caused re-renders when they were modified, often leading to inconsistencies as those two independent variables would not be changed atomically across renders leading to inconsistent, undefined behavior The commit addresses this by using a single state object instead of two, and a direct callback interface when the highlight button is pressed, which eliminates the need of relying on view re-renders to apply. Signed-off-by: Daniel D’Aquino --- damus/Components/SelectableText.swift | 56 +++++++++++++++++++-------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/damus/Components/SelectableText.swift b/damus/Components/SelectableText.swift index db62aa96..44c51ef5 100644 --- a/damus/Components/SelectableText.swift +++ b/damus/Components/SelectableText.swift @@ -13,8 +13,7 @@ struct SelectableText: View { let event: NostrEvent? let attributedString: AttributedString let textAlignment: NSTextAlignment - @State private var showHighlightPost = false - @State private var selectedText = "" + @State private var highlightPostingState: HighlightPostingState = .hide @State private var selectedTextHeight: CGFloat = .zero @State private var selectedTextWidth: CGFloat = .zero @@ -37,8 +36,9 @@ struct SelectableText: View { fixedWidth: selectedTextWidth, textAlignment: self.textAlignment, enableHighlighting: self.enableHighlighting(), - showHighlightPost: $showHighlightPost, - selectedText: $selectedText, + postHighlight: { selectedText in + self.highlightPostingState = .show_post_view(highlighted_text: selectedText) + }, height: $selectedTextHeight ) .padding([.leading, .trailing], -1.0) @@ -53,9 +53,13 @@ struct SelectableText: View { self.selectedTextWidth = newSize.width } } - .sheet(isPresented: $showHighlightPost) { - if let event { - HighlightPostView(damus_state: damus_state, event: event, selectedText: $selectedText) + .sheet(isPresented: Binding(get: { + return self.highlightPostingState.show() + }, set: { newValue in + self.highlightPostingState = newValue ? .show_post_view(highlighted_text: self.highlightPostingState.highlighted_text() ?? "") : .hide + })) { + if let event, case .show_post_view(let highlighted_text) = self.highlightPostingState { + HighlightPostView(damus_state: damus_state, event: event, selectedText: .constant(highlighted_text)) .presentationDragIndicator(.visible) .presentationDetents([.height(selectedTextHeight + 150), .medium, .large]) } @@ -66,15 +70,34 @@ struct SelectableText: View { func enableHighlighting() -> Bool { self.event != nil } + + enum HighlightPostingState { + case hide + case show_post_view(highlighted_text: String) + + func show() -> Bool { + if case .show_post_view(let highlighted_text) = self { + return true + } + return false + } + + func highlighted_text() -> String? { + switch self { + case .hide: + return nil + case .show_post_view(highlighted_text: let highlighted_text): + return highlighted_text + } + } + } } fileprivate class TextView: UITextView { - @Binding var showHighlightPost: Bool - @Binding var selectedText: String + var postHighlight: (String) -> Void - init(frame: CGRect, textContainer: NSTextContainer?, showHighlightPost: Binding, selectedText: Binding) { - self._showHighlightPost = showHighlightPost - self._selectedText = selectedText + init(frame: CGRect, textContainer: NSTextContainer?, postHighlight: @escaping (String) -> Void) { + self.postHighlight = postHighlight super.init(frame: frame, textContainer: textContainer) } @@ -91,8 +114,8 @@ fileprivate class TextView: UITextView { @objc public func highlightText(_ sender: Any?) { guard let selectedRange = self.selectedTextRange else { return } - selectedText = self.text(in: selectedRange) ?? "" - showHighlightPost.toggle() + guard let selectedText = self.text(in: selectedRange) else { return } + self.postHighlight(selectedText) } } @@ -105,12 +128,11 @@ fileprivate class TextView: UITextView { let fixedWidth: CGFloat let textAlignment: NSTextAlignment let enableHighlighting: Bool - @Binding var showHighlightPost: Bool - @Binding var selectedText: String + let postHighlight: (String) -> Void @Binding var height: CGFloat func makeUIView(context: UIViewRepresentableContext) -> TextView { - let view = TextView(frame: .zero, textContainer: nil, showHighlightPost: $showHighlightPost, selectedText: $selectedText) + let view = TextView(frame: .zero, textContainer: nil, postHighlight: postHighlight) view.isEditable = false view.dataDetectorTypes = .all view.isSelectable = true From f9271da11ccb5f8795d813cd7ab79f2843a8c73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Sat, 17 Aug 2024 14:55:23 -0700 Subject: [PATCH 03/10] Add support for rendering highlights with comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit implements rendering comments from the `["comment", ]` tag in a highlight note. Comment contents get rendered like a kind 1 note's "content" field This commit also adds the `r` "reference" tag as a standard tag reference type Changelog-Added: Add support for rendering highlights with comments Signed-off-by: Daniel D’Aquino --- damus.xcodeproj/project.pbxproj | 7 ++++++ damus/ContentParsing.swift | 2 +- damus/ContentView.swift | 2 +- damus/Models/CommentItem.swift | 23 +++++++++++++++++++ damus/Models/Contacts+.swift | 2 +- damus/Nostr/ReferencedId.swift | 19 +++++++++------ .../Events/Highlight/HighlightView.swift | 19 +++++++++++++-- nostrdb/NdbNote.swift | 7 ++++++ 8 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 damus/Models/CommentItem.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 5b1605ba..d57cebca 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -496,6 +496,9 @@ D753CEAA2BE9DE04001C3A5D /* MutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D753CEA92BE9DE04001C3A5D /* MutingTests.swift */; }; D76556D62B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */; }; D76874F32AE3632B00FB0F68 /* ProfileZapLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */; }; + D773BC5F2C6D538500349F0A /* CommentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773BC5E2C6D538500349F0A /* CommentItem.swift */; }; + D773BC602C6D538500349F0A /* CommentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773BC5E2C6D538500349F0A /* CommentItem.swift */; }; + D773BC612C6D58A700349F0A /* CommentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773BC5E2C6D538500349F0A /* CommentItem.swift */; }; D77BFA0B2AE3051200621634 /* ProfileActionSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */; }; D783A63F2AD4E53D00658DDA /* SuggestedHashtagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D783A63E2AD4E53D00658DDA /* SuggestedHashtagsView.swift */; }; D78525252A7B2EA4002FA637 /* NoteContentViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */; }; @@ -1425,6 +1428,7 @@ D753CEA92BE9DE04001C3A5D /* MutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutingTests.swift; sourceTree = ""; }; D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleWelcomeView.swift; sourceTree = ""; }; D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZapLinkView.swift; sourceTree = ""; }; + D773BC5E2C6D538500349F0A /* CommentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentItem.swift; sourceTree = ""; }; D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileActionSheetView.swift; sourceTree = ""; }; D783A63E2AD4E53D00658DDA /* SuggestedHashtagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestedHashtagsView.swift; sourceTree = ""; }; D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContentViewTests.swift; sourceTree = ""; }; @@ -1681,6 +1685,7 @@ B533694D2B66D791008A805E /* MutelistManager.swift */, D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */, 5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */, + D773BC5E2C6D538500349F0A /* CommentItem.swift */, ); path = Models; sourceTree = ""; @@ -3485,6 +3490,7 @@ 4CF38C882A9442DC00BE01B6 /* UserStatusView.swift in Sources */, 4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */, 4C1253582A76C9060004F4B8 /* PresentSheetNotify.swift in Sources */, + D773BC5F2C6D538500349F0A /* CommentItem.swift in Sources */, 4C363A962827096D006E126D /* PostBlock.swift in Sources */, 4CA9275F2A2902B20098A105 /* LongformPreview.swift in Sources */, 4C5F9116283D855D0052CD1C /* EventsModel.swift in Sources */, @@ -3720,6 +3726,7 @@ D74AAFC62B155B8B006CF0F4 /* Zaps.swift in Sources */, D7CCFC0B2B0585EA00323D86 /* nostrdb.c in Sources */, D7CE1B252B0BE1F4002EDAD4 /* sha256.c in Sources */, + D773BC612C6D58A700349F0A /* CommentItem.swift in Sources */, D7CE1B262B0BE1F8002EDAD4 /* bech32.c in Sources */, D7EDED232B117DFB0018B19C /* NoteContent.swift in Sources */, D798D21B2B0856F200234419 /* NdbTagsIterator.swift in Sources */, diff --git a/damus/ContentParsing.swift b/damus/ContentParsing.swift index cb080a1d..61daf8bc 100644 --- a/damus/ContentParsing.swift +++ b/damus/ContentParsing.swift @@ -12,7 +12,7 @@ enum NoteContent { case content(String, TagsSequence?) init(note: NostrEvent, keypair: Keypair) { - if note.known_kind == .dm { + if note.known_kind == .dm || note.known_kind == .highlight { self = .content(note.get_content(keypair), note.tags) } else { self = .note(note) diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 4371c217..1134d5c5 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -1170,7 +1170,7 @@ func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) -> } case .hashtag(let ht): result(.filter(.filter_hashtag([ht.hashtag]))) - case .param, .quote: + case .param, .quote, .reference: // doesn't really make sense here break case .naddr(let naddr): diff --git a/damus/Models/CommentItem.swift b/damus/Models/CommentItem.swift new file mode 100644 index 00000000..457d32bd --- /dev/null +++ b/damus/Models/CommentItem.swift @@ -0,0 +1,23 @@ +// +// CommentItem.swift +// damus +// +// Created by Daniel D’Aquino on 2024-08-14. +// + +import Foundation + +struct CommentItem: TagConvertible { + static let TAG_KEY: String = "comment" + let content: String + var tag: [String] { + return [Self.TAG_KEY, content] + } + + static func from_tag(tag: TagSequence) -> CommentItem? { + guard tag.count == 2 else { return nil } + guard tag[0].string() == Self.TAG_KEY else { return nil } + + return CommentItem(content: tag[1].string()) + } +} diff --git a/damus/Models/Contacts+.swift b/damus/Models/Contacts+.swift index 3ed16a22..d73df431 100644 --- a/damus/Models/Contacts+.swift +++ b/damus/Models/Contacts+.swift @@ -109,7 +109,7 @@ func is_already_following(contacts: NostrEvent, follow: FollowRef) -> Bool { case let (.pubkey(pk), .pubkey(follow_pk)): return pk == follow_pk case (.hashtag, .pubkey), (.pubkey, .hashtag), - (.event, _), (.quote, _), (.param, _), (.naddr, _): + (.event, _), (.quote, _), (.param, _), (.naddr, _), (.reference(_), _): return false } } diff --git a/damus/Nostr/ReferencedId.swift b/damus/Nostr/ReferencedId.swift index 3d45322a..e75e5941 100644 --- a/damus/Nostr/ReferencedId.swift +++ b/damus/Nostr/ReferencedId.swift @@ -122,20 +122,22 @@ enum RefId: TagConvertible, TagKeys, Equatable, Hashable { case hashtag(Hashtag) case param(TagElem) case naddr(NAddr) + case reference(String) var key: RefKey { switch self { - case .event: return .e - case .pubkey: return .p - case .quote: return .q - case .hashtag: return .t - case .param: return .d - case .naddr: return .a + case .event: return .e + case .pubkey: return .p + case .quote: return .q + case .hashtag: return .t + case .param: return .d + case .naddr: return .a + case .reference: return .r } } enum RefKey: AsciiCharacter, TagKey, CustomStringConvertible { - case e, p, t, d, q, a + case e, p, t, d, q, a, r var keychar: AsciiCharacter { self.rawValue @@ -159,6 +161,8 @@ enum RefId: TagConvertible, TagKeys, Equatable, Hashable { case .param(let string): return string.string() case .naddr(let naddr): return naddr.kind.description + ":" + naddr.author.hex() + ":" + naddr.identifier + case .reference(let string): + return string } } @@ -179,6 +183,7 @@ enum RefId: TagConvertible, TagKeys, Equatable, Hashable { case .t: return .hashtag(Hashtag(hashtag: t1.string())) case .d: return .param(t1) case .a: return .naddr(NAddr(identifier: "", author: Pubkey(Data()), relays: [], kind: 0)) + case .r: return .reference(t1.string()) } } } diff --git a/damus/Views/Events/Highlight/HighlightView.swift b/damus/Views/Events/Highlight/HighlightView.swift index 441d7041..4feefa0d 100644 --- a/damus/Views/Events/Highlight/HighlightView.swift +++ b/damus/Views/Events/Highlight/HighlightView.swift @@ -59,9 +59,9 @@ struct HighlightBodyView: View { var body: some View { Group { if options.contains(.wide) { - Main.padding(.horizontal) - } else { Main + } else { + Main.padding(.horizontal) } } } @@ -92,6 +92,18 @@ struct HighlightBodyView: View { var Main: some View { VStack(alignment: .leading, spacing: 0) { + + if self.event.event.referenced_comment_items.first?.content != nil { + let all_options = options.union(.no_action_bar) + NoteContentView( + damus_state: self.state, + event: self.event.event, + blur_images: should_blur_images(damus_state: self.state, ev: self.event.event), + size: .normal, + options: all_options + ).padding(.vertical, 10) + } + HStack { var attributedString: AttributedString { var attributedString: AttributedString = "" @@ -119,14 +131,17 @@ struct HighlightBodyView: View { RoundedRectangle(cornerRadius: 25).fill(DamusColors.highlight).frame(width: 4), alignment: .leading ) + .padding(.horizontal) .padding(.bottom, 10) if let url = event.url_ref { HighlightLink(state: state, url: url, content: event.event.content) + .padding(.horizontal) } else { if let evRef = event.event_ref { if let eventHex = hex_decode_id(evRef) { HighlightEventRef(damus_state: state, event_ref: NoteId(eventHex)) + .padding(.horizontal) .padding(.top, 5) } } diff --git a/nostrdb/NdbNote.swift b/nostrdb/NdbNote.swift index 9c0f7f8a..1d6d1908 100644 --- a/nostrdb/NdbNote.swift +++ b/nostrdb/NdbNote.swift @@ -335,6 +335,10 @@ extension NdbNote { public var referenced_mute_items: References { References(tags: self.tags) } + + public var referenced_comment_items: References { + References(tags: self.tags) + } public var references: References { References(tags: self.tags) @@ -355,6 +359,9 @@ extension NdbNote { if known_kind == .dm { return decrypted(keypair: keypair) ?? "*failed to decrypt content*" } + else if known_kind == .highlight { + return self.referenced_comment_items.first?.content ?? "" + } return content } From 40d3d273f09426321d44a15a5a82c25034ee8288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Sat, 17 Aug 2024 15:15:10 -0700 Subject: [PATCH 04/10] Add support for adding comments when creating a highlight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changelog-Added: Add support for adding comments when creating a highlight Signed-off-by: Daniel D’Aquino --- damus.xcodeproj/project.pbxproj | 8 +- damus/Components/SelectableText.swift | 9 ++- damus/ContentView.swift | 2 +- damus/Models/DraftsModel.swift | 1 + damus/Models/HighlightEvent.swift | 28 +++++++ damus/Models/Mentions.swift | 9 --- damus/Models/Post.swift | 34 +++++++- .../Highlight/HighlightDraftContentView.swift | 42 ++++++++++ .../Events/Highlight/HighlightPostView.swift | 77 ------------------ damus/Views/PostView.swift | 81 +++++++++++++------ damusTests/ReplyTests.swift | 6 +- damusTests/damusTests.swift | 4 +- 12 files changed, 175 insertions(+), 126 deletions(-) create mode 100644 damus/Views/Events/Highlight/HighlightDraftContentView.swift delete mode 100644 damus/Views/Events/Highlight/HighlightPostView.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index d57cebca..aba24942 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -399,7 +399,7 @@ 5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; }; 5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */; }; 5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; }; - 5C4D9EA72C042FA5005EA0F7 /* HighlightPostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightPostView.swift */; }; + 5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */; }; 5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; }; 5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; }; 5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; }; @@ -1343,7 +1343,7 @@ 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = ""; }; 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayNipList.swift; sourceTree = ""; }; 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = ""; }; - 5C4D9EA62C042FA5005EA0F7 /* HighlightPostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightPostView.swift; sourceTree = ""; }; + 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDraftContentView.swift; sourceTree = ""; }; 5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = ""; }; 5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = ""; }; 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = ""; }; @@ -2725,7 +2725,7 @@ 5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */, 5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */, 5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */, - 5C4D9EA62C042FA5005EA0F7 /* HighlightPostView.swift */, + 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */, ); path = Highlight; sourceTree = ""; @@ -3184,7 +3184,7 @@ 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */, 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */, - 5C4D9EA72C042FA5005EA0F7 /* HighlightPostView.swift in Sources */, + 5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */, 3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */, D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */, D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */, diff --git a/damus/Components/SelectableText.swift b/damus/Components/SelectableText.swift index 44c51ef5..3e9df355 100644 --- a/damus/Components/SelectableText.swift +++ b/damus/Components/SelectableText.swift @@ -59,9 +59,12 @@ struct SelectableText: View { self.highlightPostingState = newValue ? .show_post_view(highlighted_text: self.highlightPostingState.highlighted_text() ?? "") : .hide })) { if let event, case .show_post_view(let highlighted_text) = self.highlightPostingState { - HighlightPostView(damus_state: damus_state, event: event, selectedText: .constant(highlighted_text)) - .presentationDragIndicator(.visible) - .presentationDetents([.height(selectedTextHeight + 150), .medium, .large]) + PostView( + action: .highlighting(.init(selected_text: highlighted_text, source: .event(event))), + damus_state: damus_state + ) + .presentationDragIndicator(.visible) + .presentationDetents([.height(selectedTextHeight + 450), .medium, .large]) } } .frame(height: selectedTextHeight) diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 1134d5c5..3b1172dd 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -1109,7 +1109,7 @@ func handle_post_notification(keypair: FullKeypair, postbox: PostBox, events: Ev //let post = tup.0 //let to_relays = tup.1 print("post \(post.content)") - guard let new_ev = post_to_event(post: post, keypair: keypair) else { + guard let new_ev = post.to_event(keypair: keypair) else { return false } postbox.send(new_ev) diff --git a/damus/Models/DraftsModel.swift b/damus/Models/DraftsModel.swift index ab71d839..56d0fcac 100644 --- a/damus/Models/DraftsModel.swift +++ b/damus/Models/DraftsModel.swift @@ -28,4 +28,5 @@ class Drafts: ObservableObject { @Published var post: DraftArtifacts? = nil @Published var replies: [NostrEvent: DraftArtifacts] = [:] @Published var quotes: [NostrEvent: DraftArtifacts] = [:] + @Published var highlights: [HighlightSource: DraftArtifacts] = [:] } diff --git a/damus/Models/HighlightEvent.swift b/damus/Models/HighlightEvent.swift index 40bd0100..024c7273 100644 --- a/damus/Models/HighlightEvent.swift +++ b/damus/Models/HighlightEvent.swift @@ -32,3 +32,31 @@ struct HighlightEvent { return highlight } } + +struct HighlightContentDraft: Hashable { + let selected_text: String + let source: HighlightSource +} + +enum HighlightSource: Hashable { + case event(NostrEvent) + case external_url(URL) + + func tags() -> [[String]] { + switch self { + case .event(let event): + return [ ["e", "\(event.id)"] ] + case .external_url(let url): + return [ ["r", "\(url)"] ] + } + } + + func ref() -> RefId { + switch self { + case .event(let event): + return .event(event.id) + case .external_url(let url): + return .reference(url.absoluteString) + } + } +} diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift index 0c229fcf..2f0785f1 100644 --- a/damus/Models/Mentions.swift +++ b/damus/Models/Mentions.swift @@ -290,12 +290,3 @@ func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags { return PostTags(blocks: post_blocks, tags: new_tags) } - -func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? { - let post_blocks = parse_post_blocks(content: post.content) - let post_tags = make_post_tags(post_blocks: post_blocks, tags: post.tags) - let content = post_tags.blocks - .map(\.asString) - .joined(separator: "") - return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: post.kind.rawValue, tags: post_tags.tags) -} diff --git a/damus/Models/Post.swift b/damus/Models/Post.swift index 9a3f2a69..6a222e93 100644 --- a/damus/Models/Post.swift +++ b/damus/Models/Post.swift @@ -17,10 +17,40 @@ struct NostrPost { self.kind = kind self.tags = tags } + + func to_event(keypair: FullKeypair) -> NostrEvent? { + let post_blocks = self.parse_blocks() + let post_tags = make_post_tags(post_blocks: post_blocks, tags: self.tags) + let content = post_tags.blocks + .map(\.asString) + .joined(separator: "") + + if self.kind == .highlight { + var new_tags = post_tags.tags.filter({ $0[safe: 0] != "comment" }) + if content.count > 0 { + new_tags.append(["comment", content]) + } + return NostrEvent(content: self.content, keypair: keypair.to_keypair(), kind: self.kind.rawValue, tags: new_tags) + } + + return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: self.kind.rawValue, tags: post_tags.tags) + } + + func parse_blocks() -> [Block] { + guard let content_for_parsing = self.default_content_for_block_parsing() else { return [] } + return parse_post_blocks(content: content_for_parsing) + } + + private func default_content_for_block_parsing() -> String? { + switch kind { + case .highlight: + return tags.filter({ $0[safe: 0] == "comment" }).first?[safe: 1] + default: + return self.content + } + } } - -/// Return a list of tags func parse_post_blocks(content: String) -> [Block] { return parse_note_content(content: .content(content, nil)).blocks } diff --git a/damus/Views/Events/Highlight/HighlightDraftContentView.swift b/damus/Views/Events/Highlight/HighlightDraftContentView.swift new file mode 100644 index 00000000..9f03f309 --- /dev/null +++ b/damus/Views/Events/Highlight/HighlightDraftContentView.swift @@ -0,0 +1,42 @@ +// +// HighlightDraftContentView.swift +// damus +// +// Created by eric on 5/26/24. +// + +import SwiftUI + +struct HighlightDraftContentView: View { + let draft: HighlightContentDraft + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + HStack { + var attributedString: AttributedString { + var attributedString = AttributedString(draft.selected_text) + + if let range = attributedString.range(of: draft.selected_text) { + attributedString[range].backgroundColor = DamusColors.highlight + } + + return attributedString + } + + Text(attributedString) + .lineSpacing(5) + .padding(10) + } + .overlay( + RoundedRectangle(cornerRadius: 25).fill(DamusColors.highlight).frame(width: 4), + alignment: .leading + ) + + if case .external_url(let url) = draft.source { + LinkViewRepresentable(meta: .url(url)) + .frame(height: 50) + + } + } + } +} diff --git a/damus/Views/Events/Highlight/HighlightPostView.swift b/damus/Views/Events/Highlight/HighlightPostView.swift deleted file mode 100644 index d66e339a..00000000 --- a/damus/Views/Events/Highlight/HighlightPostView.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// HighlightPostView.swift -// damus -// -// Created by eric on 5/26/24. -// - -import SwiftUI - -struct HighlightPostView: View { - let damus_state: DamusState - let event: NostrEvent - @Binding var selectedText: String - - @Environment(\.dismiss) var dismiss - - var body: some View { - VStack(alignment: .leading, spacing: 0) { - VStack { - HStack(spacing: 5.0) { - Button(action: { - dismiss() - }, label: { - Text("Cancel", comment: "Button to cancel out of highlighting a note.") - .padding(10) - }) - .buttonStyle(NeutralButtonStyle()) - - Spacer() - - Button(NSLocalizedString("Post", comment: "Button to post a highlight.")) { - let tags: [[String]] = [ ["e", "\(self.event.id)"] ] - - let kind = NostrKind.highlight.rawValue - guard let ev = NostrEvent(content: selectedText, keypair: damus_state.keypair, kind: kind, tags: tags) else { - return - } - damus_state.postbox.send(ev) - dismiss() - } - .bold() - .buttonStyle(GradientButtonStyle(padding: 10)) - } - - Divider() - .foregroundColor(DamusColors.neutral3) - .padding(.top, 5) - } - .frame(height: 30) - .padding() - .padding(.top, 15) - - HStack { - var attributedString: AttributedString { - var attributedString = AttributedString(selectedText) - - if let range = attributedString.range(of: selectedText) { - attributedString[range].backgroundColor = DamusColors.highlight - } - - return attributedString - } - - Text(attributedString) - .lineSpacing(5) - .padding(10) - } - .overlay( - RoundedRectangle(cornerRadius: 25).fill(DamusColors.highlight).frame(width: 4), - alignment: .leading - ) - .padding() - - Spacer() - } - } -} diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift index 2f8c3e72..23b8a4ea 100644 --- a/damus/Views/PostView.swift +++ b/damus/Views/PostView.swift @@ -30,15 +30,18 @@ enum PostAction { case replying_to(NostrEvent) case quoting(NostrEvent) case posting(PostTarget) + case highlighting(HighlightContentDraft) var ev: NostrEvent? { switch self { - case .replying_to(let ev): - return ev - case .quoting(let ev): - return ev - case .posting: - return nil + case .replying_to(let ev): + return ev + case .quoting(let ev): + return ev + case .posting: + return nil + case .highlighting: + return nil } } } @@ -128,7 +131,12 @@ struct PostView: View { } var posting_disabled: Bool { - return is_post_empty || uploading_disabled + switch action { + case .highlighting(_): + return false + default: + return is_post_empty || uploading_disabled + } } // Returns a valid height for the text box, even when textHeight is not a number @@ -204,6 +212,8 @@ struct PostView: View { damus_state.drafts.quotes.removeValue(forKey: quoting) case .posting: damus_state.drafts.post = nil + case .highlighting(let draft): + damus_state.drafts.highlights.removeValue(forKey: draft.source) } } @@ -371,6 +381,9 @@ struct PostView: View { if case .quoting(let ev) = action { BuilderEventView(damus: damus_state, event: ev) } + else if case .highlighting(let draft) = action { + HighlightDraftContentView(draft: draft) + } } .padding(.horizontal) } @@ -454,14 +467,15 @@ struct PostView: View { let loaded_draft = load_draft() switch action { - case .replying_to(let replying_to): - references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to) - case .quoting(let quoting): - references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting) - case .posting(let target): - guard !loaded_draft else { break } - - fill_target_content(target: target) + case .replying_to(let replying_to): + references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to) + case .quoting(let quoting): + references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting) + case .posting(let target): + guard !loaded_draft else { break } + fill_target_content(target: target) + case .highlighting(let draft): + references = [draft.source.ref()] } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { @@ -597,6 +611,8 @@ func set_draft_for_post(drafts: Drafts, action: PostAction, artifacts: DraftArti drafts.quotes[ev] = artifacts case .posting: drafts.post = artifacts + case .highlighting(let draft): + drafts.highlights[draft.source] = artifacts } } @@ -608,6 +624,8 @@ func load_draft_for_post(drafts: Drafts, action: PostAction) -> DraftArtifacts? return drafts.quotes[ev] case .posting: return drafts.post + case .highlighting(let draft): + return drafts.highlights[draft.source] } } @@ -669,18 +687,20 @@ func build_post(state: DamusState, post: NSMutableAttributedString, action: Post var tags: [[String]] = [] switch action { - case .replying_to(let replying_to): - // start off with the reply tags - tags = nip10_reply_tags(replying_to: replying_to, keypair: state.keypair) + case .replying_to(let replying_to): + // start off with the reply tags + tags = nip10_reply_tags(replying_to: replying_to, keypair: state.keypair) - case .quoting(let ev): - content.append(" nostr:" + bech32_note_id(ev.id)) + case .quoting(let ev): + content.append(" nostr:" + bech32_note_id(ev.id)) - if let quoted_ev = state.events.lookup(ev.id) { - tags.append(["p", quoted_ev.pubkey.hex()]) - } - case .posting(let postTarget): - break + if let quoted_ev = state.events.lookup(ev.id) { + tags.append(["p", quoted_ev.pubkey.hex()]) + } + case .posting(let postTarget): + break + case .highlighting(let draft): + break } // include pubkeys @@ -690,6 +710,17 @@ func build_post(state: DamusState, post: NSMutableAttributedString, action: Post // append additional tags tags += uploadedMedias.compactMap { $0.metadata?.to_tag() } + + switch action { + case .highlighting(let draft): + tags.append(contentsOf: draft.source.tags()) + if !(content.isEmpty || content.allSatisfy { $0.isWhitespace }) { + tags.append(["comment", content]) + } + return NostrPost(content: draft.selected_text, kind: .highlight, tags: tags) + default: + break + } return NostrPost(content: content, kind: .text, tags: tags) } diff --git a/damusTests/ReplyTests.swift b/damusTests/ReplyTests.swift index fda143eb..4395a673 100644 --- a/damusTests/ReplyTests.swift +++ b/damusTests/ReplyTests.swift @@ -240,7 +240,7 @@ class ReplyTests: XCTestCase { let content = "this is a @\(pk.npub) mention" let blocks = parse_post_blocks(content: content) let post = NostrPost(content: content, tags: [["e", evid.hex()]]) - let ev = post_to_event(post: post, keypair: test_keypair_full)! + let ev = post.to_event(keypair: test_keypair_full)! XCTAssertEqual(ev.tags.count, 2) XCTAssertEqual(blocks.count, 3) @@ -255,7 +255,7 @@ class ReplyTests: XCTestCase { let content = "this is a @\(nsec) mention" let blocks = parse_post_blocks(content: content) let post = NostrPost(content: content, tags: [["e", evid.hex()]]) - let ev = post_to_event(post: post, keypair: test_keypair_full)! + let ev = post.to_event(keypair: test_keypair_full)! XCTAssertEqual(ev.tags.count, 2) XCTAssertEqual(blocks.count, 3) @@ -275,7 +275,7 @@ class ReplyTests: XCTestCase { ] let post = NostrPost(content: "this is a (@\(pubkey.npub)) mention", tags: tags) - let ev = post_to_event(post: post, keypair: test_keypair_full)! + let ev = post.to_event(keypair: test_keypair_full)! XCTAssertEqual(ev.content, "this is a (nostr:\(pubkey.npub)) mention") XCTAssertEqual(ev.tags[2][1].string(), pubkey.description) diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift index 55b1374d..71fd1df4 100644 --- a/damusTests/damusTests.swift +++ b/damusTests/damusTests.swift @@ -193,7 +193,7 @@ class damusTests: XCTestCase { func testMakeHashtagPost() { let post = NostrPost(content: "#damus some content #bitcoin derp #かっこいい wow", tags: []) - let ev = post_to_event(post: post, keypair: test_keypair_full)! + let ev = post.to_event(keypair: test_keypair_full)! XCTAssertEqual(ev.tags.count, 3) XCTAssertEqual(ev.content, "#damus some content #bitcoin derp #かっこいい wow") @@ -270,7 +270,7 @@ class damusTests: XCTestCase { private func createEventFromContentString(_ content: String) -> NostrEvent { let post = NostrPost(content: content, tags: []) - guard let ev = post_to_event(post: post, keypair: test_keypair_full) else { + guard let ev = post.to_event(keypair: test_keypair_full) else { XCTFail("Could not create event") return test_note } From 55090bc1026bc9ad0c95d3d1522873a6418928da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Sat, 17 Aug 2024 15:38:27 -0700 Subject: [PATCH 05/10] Add highlighter extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a highlighting extension for web pages. This works on Safari, and can be used by selecting a text on a page and hitting the share button at the bottom of the Safari UI To make this possible, some refactoring was necessary: 1. Several sources were included in the extension bundle to provide access to DamusState, PostView, and the postbox 2. UIApplication.shared was replaced with `this_app`, which routes to UIApplication.shared on the main app bundle, and routes to a bogus UIApplication() in the extension. This is needed because UIApplication.shared cannot be used on an extension. 3. Some items were moved to different files to facilitate the transition. The extension itself uses PostView, and implements views for several edge cases, and tries to handle the note publishing process gracefully. Changelog-Added: Add highlighter for web pages Signed-off-by: Daniel D’Aquino --- damus.xcodeproj/project.pbxproj | 1160 ++++++++++++++++- .../HighlighterActionExtension.xcscheme | 101 ++ damus/Components/InvoiceView.swift | 13 +- damus/ContentView.swift | 6 +- damus/Models/Camera/CameraService.swift | 2 +- damus/Util/DamusAliases.swift | 11 + damus/Util/InputDismissKeyboard.swift | 2 +- damus/Util/Theme.swift | 3 +- damus/Views/AddRelayView.swift | 2 +- damus/Views/Muting/AddMuteItemView.swift | 2 +- .../DamusAppNotificationView.swift | 2 +- damus/Views/Zaps/CustomizeZapView.swift | 2 +- .../ActionViewController.swift | 250 ++++ .../HighlighterExtensionAliases.swift | 11 + highlighter action extension/Info.plist | 30 + .../AppIconExtension.appiconset/Contents.json | 14 + .../highlighter.png | Bin 0 -> 52889 bytes .../Media.xcassets/Contents.json | 6 + .../TouchBarBezel.colorset/Contents.json | 14 + highlighter action extension/getSelection.js | 12 + .../highlighter action extension.entitlements | 18 + 21 files changed, 1642 insertions(+), 19 deletions(-) create mode 100644 damus.xcodeproj/xcshareddata/xcschemes/HighlighterActionExtension.xcscheme create mode 100644 damus/Util/DamusAliases.swift create mode 100644 highlighter action extension/ActionViewController.swift create mode 100644 highlighter action extension/HighlighterExtensionAliases.swift create mode 100644 highlighter action extension/Info.plist create mode 100644 highlighter action extension/Media.xcassets/AppIconExtension.appiconset/Contents.json create mode 100644 highlighter action extension/Media.xcassets/AppIconExtension.appiconset/highlighter.png create mode 100644 highlighter action extension/Media.xcassets/Contents.json create mode 100644 highlighter action extension/Media.xcassets/TouchBarBezel.colorset/Contents.json create mode 100644 highlighter action extension/getSelection.js create mode 100644 highlighter action extension/highlighter action extension.entitlements diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index aba24942..adfb6c9f 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -453,6 +453,125 @@ BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; }; BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; }; D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; }; + D703D7192C66E47100A400EA /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D703D7182C66E47100A400EA /* UniformTypeIdentifiers.framework */; }; + D703D71C2C66E47100A400EA /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D703D71B2C66E47100A400EA /* Media.xcassets */; }; + D703D71E2C66E47100A400EA /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D703D71D2C66E47100A400EA /* ActionViewController.swift */; }; + D703D7252C66E47100A400EA /* HighlighterActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D703D7172C66E47100A400EA /* HighlighterActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + D703D72B2C66F29500A400EA /* getSelection.js in Resources */ = {isa = PBXBuildFile; fileRef = D703D72A2C66F29500A400EA /* getSelection.js */; }; + D703D7432C67084F00A400EA /* Ndb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C478E242A9932C100489948 /* Ndb.swift */; }; + D703D7442C67086800A400EA /* HeadlessDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */; }; + D703D7452C67090200A400EA /* MutelistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533694D2B66D791008A805E /* MutelistManager.swift */; }; + D703D7462C67091A00A400EA /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; }; + D703D7472C67092700A400EA /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; }; + D703D7492C6709B100A400EA /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = D703D7482C6709B100A400EA /* secp256k1 */; }; + D703D74A2C6709C200A400EA /* MuteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */; }; + D703D74B2C6709C900A400EA /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; }; + D703D74C2C6709CE00A400EA /* Zaps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883A72975FC1800DC99E7 /* Zaps.swift */; }; + D703D74D2C6709D400A400EA /* Zap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAE6297EFA7B00430951 /* Zap.swift */; }; + D703D74E2C6709DA00A400EA /* Pubkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF02A73FCDB007AEB17 /* Pubkey.swift */; }; + D703D74F2C6709ED00A400EA /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; }; + D703D7502C6709F500A400EA /* NdbTxn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3DCC752A9FC2030091E592 /* NdbTxn.swift */; }; + D703D7512C6709FB00A400EA /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA527FF87A20006080F /* Nostr.swift */; }; + D703D7522C670A1400A400EA /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; }; + D703D7532C670A2600A400EA /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; }; + D703D7542C670A2A00A400EA /* MediaUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CB5D5B2B1176B200AD4105 /* MediaUploader.swift */; }; + D703D7552C670A3700A400EA /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; }; + D703D7562C670A4C00A400EA /* TranslationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */; }; + D703D7572C670A5A00A400EA /* IdType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FEE2A73FCCB007AEB17 /* IdType.swift */; }; + D703D7582C670A6000A400EA /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; }; + D703D7592C670A7300A400EA /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; }; + D703D75A2C670A7900A400EA /* LNUrls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883B5297730E400DC99E7 /* LNUrls.swift */; }; + D703D75B2C670A7F00A400EA /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79A28306D7B00E1F516 /* Contacts.swift */; }; + D703D75C2C670A8400A400EA /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; }; + D703D75D2C670A8E00A400EA /* ReferencedId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */; }; + D703D75E2C670A9A00A400EA /* NdbTagElem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDD1ADF2A6B305F001CD4DF /* NdbTagElem.swift */; }; + D703D75F2C670AA200A400EA /* NostrEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB228049D640006080F /* NostrEvent.swift */; }; + D703D7602C670AAB00A400EA /* MigratedTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D798D21D2B0858BB00234419 /* MigratedTypes.swift */; }; + D703D7612C670AC000A400EA /* FlatBufferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9492A9AD44700DC3548 /* FlatBufferObject.swift */; }; + D703D7622C670ACB00A400EA /* ByteBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9402A9AD44700DC3548 /* ByteBuffer.swift */; }; + D703D7632C670ADD00A400EA /* FollowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CB5D5E2B11770C00AD4105 /* FollowState.swift */; }; + D703D7642C670AE300A400EA /* StringCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */; }; + D703D7652C670AF500A400EA /* NdbTagIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9054882A6AED4700811EEC /* NdbTagIterator.swift */; }; + D703D7662C670AFC00A400EA /* AsciiCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D5C9C2A6B2CB40024563C /* AsciiCharacter.swift */; }; + D703D7672C670B0F00A400EA /* ZapType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CB5D4A2B11721600AD4105 /* ZapType.swift */; }; + D703D7682C670B1400A400EA /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; }; + D703D7692C670B2600A400EA /* Block.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7527271D2A93FF0100214108 /* Block.swift */; }; + D703D76A2C670B2C00A400EA /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; }; + D703D76B2C670B3100A400EA /* Referenced.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF82A741939007AEB17 /* Referenced.swift */; }; + D703D76C2C670B3900A400EA /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A93282704FA006E126D /* Post.swift */; }; + D703D76D2C670B4500A400EA /* ZapDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFCE2B155D8C006CF0F4 /* ZapDataModel.swift */; }; + D703D76E2C670B4900A400EA /* NdbTagsIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDD1AE12A6B3074001CD4DF /* NdbTagsIterator.swift */; }; + D703D76F2C670B5200A400EA /* NostrResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB028049D510006080F /* NostrResponse.swift */; }; + D703D7702C670B5F00A400EA /* UserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5E54022A9522F600FF6E60 /* UserStatus.swift */; }; + D703D7712C670B6D00A400EA /* NdbProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C478E2C2A9935D300489948 /* NdbProfile.swift */; }; + D703D7722C670B8000A400EA /* FlatBufferBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93B2A9AD44700DC3548 /* FlatBufferBuilder.swift */; }; + D703D7732C670B8500A400EA /* Offset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9382A9AD44700DC3548 /* Offset.swift */; }; + D703D7742C670B8A00A400EA /* FbConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9372A9AD44700DC3548 /* FbConstants.swift */; }; + D703D7752C670BBF00A400EA /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; + D703D7762C670BCA00A400EA /* Verifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93E2A9AD44700DC3548 /* Verifier.swift */; }; + D703D7772C670BCE00A400EA /* Verifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9452A9AD44700DC3548 /* Verifiable.swift */; }; + D703D7782C670BD900A400EA /* LNUrlPayRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883A52975F83C00DC99E7 /* LNUrlPayRequest.swift */; }; + D703D7792C670BE100A400EA /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; }; + D703D77A2C670BEB00A400EA /* VeriferOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9432A9AD44700DC3548 /* VeriferOptions.swift */; }; + D703D77B2C670BF000A400EA /* TableVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9412A9AD44700DC3548 /* TableVerifier.swift */; }; + D703D77C2C670BFB00A400EA /* Enum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B94A2A9AD44700DC3548 /* Enum.swift */; }; + D703D77D2C670C0300A400EA /* FlatbuffersErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93C2A9AD44700DC3548 /* FlatbuffersErrors.swift */; }; + D703D77E2C670C1100A400EA /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; }; + D703D77F2C670C1600A400EA /* ThreadReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C45E5012BED4D000025A428 /* ThreadReply.swift */; }; + D703D7802C670C2500A400EA /* NIP05.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838529656C8B00DC99E7 /* NIP05.swift */; }; + D703D7812C670C2B00A400EA /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; }; + D703D7822C670C3400A400EA /* InsertSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA728297703006E126D /* InsertSort.swift */; }; + D703D7832C670C3900A400EA /* damus.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670A28FDE64700038D2A /* damus.c */; }; + D703D7842C670C4700A400EA /* SequenceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED202B117DCA0018B19C /* SequenceUtils.swift */; }; + D703D7852C670C6100A400EA /* Notify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA3529F2A76AE80003BB08B /* Notify.swift */; }; + D703D7862C670C6500A400EA /* NewUnmutesNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352AB2A76C07F003BB08B /* NewUnmutesNotify.swift */; }; + D703D7872C670C7E00A400EA /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; }; + D703D7882C670C8200A400EA /* FriendFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CB5D502B1174D100AD4105 /* FriendFilter.swift */; }; + D703D7892C670C8600A400EA /* DeepLPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */; }; + D703D78A2C670C8A00A400EA /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; }; + D703D78B2C670C9500A400EA /* MakeZapRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFCB2B155D07006CF0F4 /* MakeZapRequest.swift */; }; + D703D78C2C670CAB00A400EA /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */; }; + D703D78D2C670CAF00A400EA /* UpdateStatsNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A32A76AFF3003BB08B /* UpdateStatsNotify.swift */; }; + D703D78E2C670CEF00A400EA /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9442A9AD44700DC3548 /* Table.swift */; }; + D703D78F2C670D0300A400EA /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09612A098D0E00943473 /* WalletConnect.swift */; }; + D703D7902C670D1600A400EA /* NewEventsBits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CB5D4D2B11728000AD4105 /* NewEventsBits.swift */; }; + D703D7912C670D1E00A400EA /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; }; + D703D7922C670D2900A400EA /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; }; + D703D7932C670DAF00A400EA /* mem.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA66428FF5F6800C48A62 /* mem.c */; }; + D703D7942C670DE300A400EA /* bolt11.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA63C28FF52D600C48A62 /* bolt11.c */; }; + D703D7952C670DE600A400EA /* hash_u5.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA64028FF553900C48A62 /* hash_u5.c */; }; + D703D7962C670DEA00A400EA /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FF2A2A891E00DDEA40 /* error.c */; }; + D703D7972C670DED00A400EA /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276E2A2A5D110098A105 /* wasm.c */; }; + D703D7982C670DF200A400EA /* utf8.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670D28FDEAA000038D2A /* utf8.c */; }; + D703D7992C670DF900A400EA /* sha256.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA64328FF558100C48A62 /* sha256.c */; }; + D703D79A2C670DFD00A400EA /* bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA64828FF597700C48A62 /* bech32.c */; }; + D703D79B2C670E0000A400EA /* bech32_util.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA64B28FF59AC00C48A62 /* bech32_util.c */; }; + D703D79C2C670E0300A400EA /* tal.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA64E28FF59F200C48A62 /* tal.c */; }; + D703D79D2C670E0700A400EA /* node_id.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA65F28FF5E7700C48A62 /* node_id.c */; }; + D703D79E2C670E0F00A400EA /* hex.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA66728FF5F9900C48A62 /* hex.c */; }; + D703D79F2C670E1200A400EA /* amount.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA66C28FF782800C48A62 /* amount.c */; }; + D703D7A02C670E1500A400EA /* take.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67428FF7A5A00C48A62 /* take.c */; }; + D703D7A12C670E1700A400EA /* talstr.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67628FF7A9800C48A62 /* talstr.c */; }; + D703D7A22C670E1A00A400EA /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67828FF7ABF00C48A62 /* list.c */; }; + D703D7A32C670E1D00A400EA /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CE29E38B950036AF10 /* nostr_bech32.c */; }; + D703D7A42C670E3C00A400EA /* midl.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4793032A993DB900489948 /* midl.c */; }; + D703D7A52C670E3E00A400EA /* mdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4793002A993B9A00489948 /* mdb.c */; }; + D703D7A62C670E5200A400EA /* builder.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4792942A9939BD00489948 /* builder.c */; }; + D703D7A72C670E5500A400EA /* json_parser.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4792C82A9939BD00489948 /* json_parser.c */; }; + D703D7A82C670E5800A400EA /* emitter.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4792CF2A9939BD00489948 /* emitter.c */; }; + D703D7A92C670E5A00A400EA /* refmap.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4792D12A9939BD00489948 /* refmap.c */; }; + D703D7AA2C670E5D00A400EA /* verifier.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4792D42A9939BD00489948 /* verifier.c */; }; + D703D7AB2C670F6900A400EA /* UnmuteThreadNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4E137C2A76D63600BDD832 /* UnmuteThreadNotify.swift */; }; + D703D7AF2C670FB700A400EA /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = D703D7AE2C670FB700A400EA /* MarkdownUI */; }; + D703D7B02C6710A500A400EA /* Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9422A9AD44700DC3548 /* Root.swift */; }; + D703D7B12C6710AB00A400EA /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; }; + D703D7B22C6710AF00A400EA /* ContentParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4DD3DA2A6CA7E8005B4E85 /* ContentParsing.swift */; }; + D703D7B32C6710BF00A400EA /* NewMutesNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A72A76B37E003BB08B /* NewMutesNotify.swift */; }; + D703D7B42C6710F200A400EA /* Int+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93A2A9AD44700DC3548 /* Int+extension.swift */; }; + D703D7B52C67111C00A400EA /* CollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */; }; + D703D7B62C67118200A400EA /* String+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9472A9AD44700DC3548 /* String+extension.swift */; }; + D703D7B72C67118F00A400EA /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; }; + D703D7B82C6711A000A400EA /* NativeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9462A9AD44700DC3548 /* NativeObject.swift */; }; D70A3B172B02DCE5008BD568 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */; }; D7100C562B76F8E600C59298 /* PurpleViewPrimitives.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C552B76F8E600C59298 /* PurpleViewPrimitives.swift */; }; D7100C582B76FC8400C59298 /* MarketingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C572B76FC8400C59298 /* MarketingContentView.swift */; }; @@ -476,6 +595,365 @@ D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; }; D7373BA82B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */; }; D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA92B68A65A00F7783D /* PurpleAccountUpdateNotify.swift */; }; + D73E5E162C6A9619007EB227 /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; }; + D73E5E172C6A962A007EB227 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; }; + D73E5E182C6A963D007EB227 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; }; + D73E5E192C6A965A007EB227 /* DamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFDF281DE1ED00B3DE84 /* DamusState.swift */; }; + D73E5E1A2C6A9665007EB227 /* RelayPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB628049D990006080F /* RelayPool.swift */; }; + D73E5E1B2C6A9672007EB227 /* LikeCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD9281DCA1400B3DE84 /* LikeCounter.swift */; }; + D73E5E1C2C6A9677007EB227 /* DirectMessagesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */; }; + D73E5E1D2C6A9680007EB227 /* PreviewCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3A1D3629637E0500558C0F /* PreviewCache.swift */; }; + D73E5E1E2C6A9694007EB227 /* RelayFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794729941DA700F758CC /* RelayFilters.swift */; }; + D73E5E1F2C6A969E007EB227 /* RelayModelCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A82A3495B6006AE6DC /* RelayModelCache.swift */; }; + D73E5E202C6A97F4007EB227 /* AttachedWalletNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C86F7C52A76C51100EC0817 /* AttachedWalletNotify.swift */; }; + D73E5E212C6A97F4007EB227 /* DisplayTabBarNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9D6D152B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift */; }; + D73E5E222C6A97F4007EB227 /* BroadcastNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253552A76C8C60004F4B8 /* BroadcastNotify.swift */; }; + D73E5E232C6A97F4007EB227 /* ComposeNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253512A76C6130004F4B8 /* ComposeNotify.swift */; }; + D73E5E242C6A97F4007EB227 /* FollowedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352AD2A76C1AC003BB08B /* FollowedNotify.swift */; }; + D73E5E252C6A97F4007EB227 /* FollowNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA3529D2A76AE67003BB08B /* FollowNotify.swift */; }; + D73E5E262C6A97F4007EB227 /* LikedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A12A76AEC5003BB08B /* LikedNotify.swift */; }; + D73E5E272C6A97F4007EB227 /* LocalNotificationNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A92A76BF3A003BB08B /* LocalNotificationNotify.swift */; }; + D73E5E282C6A97F4007EB227 /* LoginNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C12535B2A76CA540004F4B8 /* LoginNotify.swift */; }; + D73E5E292C6A97F4007EB227 /* LogoutNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253532A76C7D60004F4B8 /* LogoutNotify.swift */; }; + D73E5E2A2C6A97F4007EB227 /* OnlyZapsNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253652A76D0FF0004F4B8 /* OnlyZapsNotify.swift */; }; + D73E5E2B2C6A97F4007EB227 /* PostNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253612A76D00B0004F4B8 /* PostNotify.swift */; }; + D73E5E2C2C6A97F4007EB227 /* PresentSheetNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253572A76C9060004F4B8 /* PresentSheetNotify.swift */; }; + D73E5E2D2C6A97F4007EB227 /* ProfileUpdatedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C59B98B2A76C2550032FFEB /* ProfileUpdatedNotify.swift */; }; + D73E5E2E2C6A97F4007EB227 /* ReportNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253632A76D08F0004F4B8 /* ReportNotify.swift */; }; + D73E5E2F2C6A97F4007EB227 /* ScrollToTopNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C12535F2A76CF890004F4B8 /* ScrollToTopNotify.swift */; }; + D73E5E302C6A97F4007EB227 /* SwitchedTimelineNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C12535D2A76CA870004F4B8 /* SwitchedTimelineNotify.swift */; }; + D73E5E312C6A97F4007EB227 /* UnfollowedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C12534F2A76C5B20004F4B8 /* UnfollowedNotify.swift */; }; + D73E5E322C6A97F4007EB227 /* UnfollowNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253592A76C9960004F4B8 /* UnfollowNotify.swift */; }; + D73E5E332C6A97F4007EB227 /* ZappingNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C86F7C32A76C44C00EC0817 /* ZappingNotify.swift */; }; + D73E5E342C6A97F4007EB227 /* MuteNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253672A76D2470004F4B8 /* MuteNotify.swift */; }; + D73E5E352C6A97F4007EB227 /* RelaysChangedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253692A76D3850004F4B8 /* RelaysChangedNotify.swift */; }; + D73E5E362C6A97F4007EB227 /* MuteThreadNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4E137A2A76D5FB00BDD832 /* MuteThreadNotify.swift */; }; + D73E5E372C6A97F4007EB227 /* ReconnectRelaysNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C612B312BD700A232C0 /* ReconnectRelaysNotify.swift */; }; + D73E5E382C6A97F4007EB227 /* PurpleAccountUpdateNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA92B68A65A00F7783D /* PurpleAccountUpdateNotify.swift */; }; + D73E5E392C6A97F4007EB227 /* DamusDuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C60C222B532A8700C5ECA7 /* DamusDuration.swift */; }; + D73E5E3A2C6A97F4007EB227 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; }; + D73E5E3B2C6A97F4007EB227 /* MusicController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64305B2A945AFF00B0C0E9 /* MusicController.swift */; }; + D73E5E3C2C6A97F4007EB227 /* UserStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF38C872A9442DC00BE01B6 /* UserStatusView.swift */; }; + D73E5E3E2C6A97F4007EB227 /* SearchHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C687C232A5FA86D0092C550 /* SearchHeaderView.swift */; }; + D73E5E3F2C6A97F4007EB227 /* DamusGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09712A0AEF5E00943473 /* DamusGradient.swift */; }; + D73E5E402C6A97F4007EB227 /* AlbyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09732A0AEF9000943473 /* AlbyGradient.swift */; }; + D73E5E412C6A97F4007EB227 /* GoldSupportGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2859612A12A7F0004746F7 /* GoldSupportGradient.swift */; }; + D73E5E422C6A97F4007EB227 /* PinkGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAE2A194075008FC15A /* PinkGradient.swift */; }; + D73E5E432C6A97F4007EB227 /* GrayGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F72A6983AF001F4053 /* GrayGradient.swift */; }; + D73E5E442C6A97F4007EB227 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; }; + D73E5E452C6A97F4007EB227 /* DamusBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C687C202A5F7ED00092C550 /* DamusBackground.swift */; }; + D73E5E462C6A97F4007EB227 /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; }; + D73E5E472C6A97F4007EB227 /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; }; + D73E5E482C6A97F4007EB227 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; }; + D73E5E492C6A97F4007EB227 /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; }; + D73E5E4D2C6A97F4007EB227 /* NIP05Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */; }; + D73E5E4E2C6A97F4007EB227 /* Reposted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838C296F710400DC99E7 /* Reposted.swift */; }; + D73E5E4F2C6A97F4007EB227 /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; }; + D73E5E502C6A97F4007EB227 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEC297F0B9E00430951 /* Highlight.swift */; }; + D73E5E512C6A97F4007EB227 /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; }; + D73E5E522C6A97F4007EB227 /* UserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE22981BC7D00D66079 /* UserView.swift */; }; + D73E5E532C6A97F4007EB227 /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; }; + D73E5E542C6A97F4007EB227 /* NoteZapButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883AF297705DD00DC99E7 /* NoteZapButton.swift */; }; + D73E5E552C6A97F4007EB227 /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C42812B298C848200DBF26F /* TranslateView.swift */; }; + D73E5E562C6A97F4007EB227 /* SelectableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFF6316299FEFE5005D382A /* SelectableText.swift */; }; + D73E5E572C6A97F4007EB227 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; }; + D73E5E582C6A97F4007EB227 /* ThiccDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F729DB7399005914DB /* ThiccDivider.swift */; }; + D73E5E592C6A97F4007EB227 /* IconLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */; }; + D73E5E5A2C6A97F4007EB227 /* TruncatedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00C929DF80350036AF10 /* TruncatedText.swift */; }; + D73E5E5B2C6A97F4007EB227 /* SupporterBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */; }; + D73E5E5C2C6A97F4007EB227 /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; }; + D73E5E5D2C6A97F4007EB227 /* NeutralButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */; }; + D73E5E5E2C6A97F4007EB227 /* URIParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04A37C52B544F090029650D /* URIParsing.swift */; }; + D73E5E5F2C6A97F4007EB227 /* VersionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1D4FB02A7958E60024F453 /* VersionInfo.swift */; }; + D73E5E602C6A97F4007EB227 /* ImageMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C198DF429F88D2E004C165C /* ImageMetadata.swift */; }; + D73E5E612C6A97F4007EB227 /* ImageProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0EE9DD32B8E5FEA00F3002D /* ImageProcessing.swift */; }; + D73E5E622C6A97F4007EB227 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */; }; + D73E5E632C6A97F4007EB227 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C198DEE29F88C6B004C165C /* BlurHashDecode.swift */; }; + D73E5E642C6A97F4007EB227 /* PostBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F329D779B5005914DB /* PostBox.swift */; }; + D73E5E652C6A97F4007EB227 /* KFOptionSetter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */; }; + D73E5E662C6A97F4007EB227 /* FillAndStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09752A0AF19E00943473 /* FillAndStroke.swift */; }; + D73E5E672C6A97F4007EB227 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12772BEED22400F4F781 /* Array.swift */; }; + D73E5E682C6A97F4007EB227 /* VectorMath.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78DB85A2C20FE4F00F0AB12 /* VectorMath.swift */; }; + D73E5E692C6A97F4007EB227 /* RelayBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC6193929DC777C006A86D1 /* RelayBootstrap.swift */; }; + D73E5E6A2C6A97F4007EB227 /* RelayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A62A34915F006AE6DC /* RelayModel.swift */; }; + D73E5E6B2C6A97F4007EB227 /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE829844AF100D66079 /* AnyCodable.swift */; }; + D73E5E6C2C6A97F4007EB227 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */; }; + D73E5E6D2C6A97F4007EB227 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; }; + D73E5E6E2C6A97F4007EB227 /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; }; + D73E5E6F2C6A97F4007EB227 /* TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */; }; + D73E5E702C6A97F4007EB227 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8328233689006E126D /* Parser.swift */; }; + D73E5E722C6A97F4007EB227 /* LinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3165648A295B70D500C64604 /* LinkView.swift */; }; + D73E5E742C6A97F4007EB227 /* Lists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD72981980C00D66079 /* Lists.swift */; }; + D73E5E752C6A97F4007EB227 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; }; + D73E5E762C6A97F4007EB227 /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; }; + D73E5E772C6A97F4007EB227 /* Translator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB72AB8298ECF30004BB58C /* Translator.swift */; }; + D73E5E782C6A97F4007EB227 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; }; + D73E5E792C6A97F4007EB227 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; }; + D73E5E7A2C6A97F4007EB227 /* EventCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7729A577AB00E2BD5A /* EventCache.swift */; }; + D73E5E7B2C6A97F4007EB227 /* DebouncedOnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */; }; + D73E5E7C2C6A97F4007EB227 /* ReplyCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */; }; + D73E5E7D2C6A97F4007EB227 /* CompatibleAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */; }; + D73E5E7E2C6A97F4007EB227 /* Hashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */; }; + D73E5E7F2C6A97F4007EB227 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128B29EB19C40006FA5A /* LocalNotification.swift */; }; + D73E5E802C6A97F4007EB227 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; }; + D73E5E812C6A97F4007EB227 /* KeyboardVisible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */; }; + D73E5E832C6A97F4007EB227 /* AVPlayer+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */; }; + D73E5E842C6A97F4007EB227 /* Zaps+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */; }; + D73E5E852C6A97F4007EB227 /* WalletConnect+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */; }; + D73E5E862C6A97F4007EB227 /* DamusPurpleNotificationManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CBD1D32B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift */; }; + D73E5E872C6A97F4007EB227 /* DamusPurple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F43092B23F0BE00425B75 /* DamusPurple.swift */; }; + D73E5E882C6A97F4007EB227 /* StoreObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F430B2B23FB9B00425B75 /* StoreObserver.swift */; }; + D73E5E892C6A97F4007EB227 /* DamusPurpleURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3DD2B53854300F104C4 /* DamusPurpleURL.swift */; }; + D73E5E8A2C6A97F4007EB227 /* PurpleStoreKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */; }; + D73E5E8D2C6A97F4007EB227 /* CameraService+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598F2ABCCEBA0018D73B /* CameraService+Extensions.swift */; }; + D73E5E8E2C6A97F4007EB227 /* ImageResizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3759892ABCCDE30018D73B /* ImageResizer.swift */; }; + D73E5E8F2C6A97F4007EB227 /* PhotoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */; }; + D73E5E902C6A97F4007EB227 /* VideoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */; }; + D73E5E912C6A97F4007EB227 /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */; }; + D73E5E922C6A97F4007EB227 /* EventGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0929A55429003E4487 /* EventGroup.swift */; }; + D73E5E932C6A97F4007EB227 /* ZapGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0B29A5543C003E4487 /* ZapGroup.swift */; }; + D73E5E942C6A97F4007EB227 /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; }; + D73E5E952C6A97F4007EB227 /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F8E280F640A000448DE /* ThreadModel.swift */; }; + D73E5E962C6A97F4007EB227 /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; }; + D73E5E972C6A97F4007EB227 /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */; }; + D73E5E982C6A97F4007EB227 /* ActionBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */; }; + D73E5E992C6A97F4007EB227 /* Liked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFDB281DCE6100B3DE84 /* Liked.swift */; }; + D73E5E9A2C6A97F4007EB227 /* ProfileUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A912825FCF2006E126D /* ProfileUpdate.swift */; }; + D73E5E9B2C6A97F4007EB227 /* PostBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A952827096D006E126D /* PostBlock.swift */; }; + D73E5E9C2C6A97F4007EB227 /* Reply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9928283854006E126D /* Reply.swift */; }; + D73E5E9D2C6A97F4007EB227 /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA328296DEE006E126D /* SearchModel.swift */; }; + D73E5E9E2C6A97F4007EB227 /* NostrFilter+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8A4BB62AE4359200065E81 /* NostrFilter+Hashable.swift */; }; + D73E5E9F2C6A97F4007EB227 /* CreateAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C85283892E7008A31F1 /* CreateAccountModel.swift */; }; + D73E5EA12C6A97F4007EB227 /* SignalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C633351283D419F00B1C9C3 /* SignalModel.swift */; }; + D73E5EA22C6A97F4007EB227 /* FollowTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9113283D694D0052CD1C /* FollowTarget.swift */; }; + D73E5EA32C6A97F4007EB227 /* BookmarksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12C29A1855400E10810 /* BookmarksManager.swift */; }; + D73E5EA42C6A97F4007EB227 /* EventsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9115283D855D0052CD1C /* EventsModel.swift */; }; + D73E5EA52C6A97F4007EB227 /* FollowingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9117283D88E40052CD1C /* FollowingModel.swift */; }; + D73E5EA62C6A97F4007EB227 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; }; + D73E5EA72C6A97F4007EB227 /* SearchHomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E67284ED36500A22DF5 /* SearchHomeModel.swift */; }; + D73E5EA82C6A97F4007EB227 /* DirectMessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F372871EDE300040376 /* DirectMessageModel.swift */; }; + D73E5EA92C6A97F4007EB227 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD32980996B00D66079 /* Report.swift */; }; + D73E5EAA2C6A97F4007EB227 /* ZapsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8795A2996C47A00F758CC /* ZapsModel.swift */; }; + D73E5EAB2C6A97F4007EB227 /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; }; + D73E5EAC2C6A97F4007EB227 /* NotificationsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0629A540BA003E4487 /* NotificationsModel.swift */; }; + D73E5EAD2C6A97F4007EB227 /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; }; + D73E5EAE2C6A97F4007EB227 /* WalletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09772A0B0CC900943473 /* WalletModel.swift */; }; + D73E5EAF2C6A97F4007EB227 /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; }; + D73E5EB02C6A97F4007EB227 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; }; + D73E5EB12C6A97F4007EB227 /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; }; + D73E5EB22C6A97F4007EB227 /* NotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CB5D3D2B116DAD00AD4105 /* NotificationsManager.swift */; }; + D73E5EB32C6A97F4007EB227 /* Contacts+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CB5D442B116FE800AD4105 /* Contacts+.swift */; }; + D73E5EB42C6A97F4007EB227 /* NoteContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED1B2B1178FE0018B19C /* NoteContent.swift */; }; + D73E5EB52C6A97F4007EB227 /* LongformEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED1D2B11797D0018B19C /* LongformEvent.swift */; }; + D73E5EB62C6A97F4007EB227 /* PushNotificationClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */; }; + D73E5EB72C6A97F4007EB227 /* HighlightEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */; }; + D73E5EB82C6A97F4007EB227 /* RelayConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */; }; + D73E5EB92C6A97F4007EB227 /* RelayLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A60D132A28BEEE00186190 /* RelayLog.swift */; }; + D73E5EBA2C6A97F4007EB227 /* NostrFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAE28049D340006080F /* NostrFilter.swift */; }; + D73E5EBB2C6A97F4007EB227 /* Nip98HTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */; }; + D73E5EBC2C6A97F4007EB227 /* Relay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB428049D790006080F /* Relay.swift */; }; + D73E5EBD2C6A97F4007EB227 /* NostrRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */; }; + D73E5EBE2C6A97F4007EB227 /* NostrLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8F28247A1D006E126D /* NostrLink.swift */; }; + D73E5EBF2C6A97F4007EB227 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; }; + D73E5EC02C6A97F4007EB227 /* NostrEvent+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D798D22B2B086C7400234419 /* NostrEvent+.swift */; }; + D73E5EC12C6A97F4007EB227 /* NIP98AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */; }; + D73E5EC22C6A97F4007EB227 /* NostrAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C652B312C3700A232C0 /* NostrAuth.swift */; }; + D73E5EC42C6A97F4007EB227 /* ReplyQuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B602BD0B25C002F2F9B /* ReplyQuoteView.swift */; }; + D73E5EC62C6A97F4007EB227 /* ChatBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78DB85E2C20FED300F0AB12 /* ChatBubbleView.swift */; }; + D73E5EC72C6A97F4007EB227 /* VisibilityTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */; }; + D73E5EC82C6A97F4007EB227 /* CameraPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3759962ABCCF360018D73B /* CameraPreview.swift */; }; + D73E5EC92C6A97F4007EB227 /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02429942B7E97740088B16C /* CameraController.swift */; }; + D73E5ECA2C6A97F4007EB227 /* OnboardingSuggestionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694E92A662232001F4053 /* OnboardingSuggestionsView.swift */; }; + D73E5ECB2C6A97F4007EB227 /* SuggestedUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F12A67314D001F4053 /* SuggestedUserView.swift */; }; + D73E5ECC2C6A97F4007EB227 /* SuggestedUsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694EB2A662292001F4053 /* SuggestedUsersViewModel.swift */; }; + D73E5ECE2C6A97F4007EB227 /* CodeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096A2A0AEA0400943473 /* CodeScanner.swift */; }; + D73E5ECF2C6A97F4007EB227 /* ScannerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096B2A0AEA0400943473 /* ScannerCoordinator.swift */; }; + D73E5ED02C6A97F4007EB227 /* ScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096C2A0AEA0400943473 /* ScannerViewController.swift */; }; + D73E5ED22C6A97F4007EB227 /* WalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095D2A098C5D00943473 /* WalletView.swift */; }; + D73E5ED32C6A97F4007EB227 /* NWCScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09672A0AE9B200943473 /* NWCScannerView.swift */; }; + D73E5ED42C6A97F4007EB227 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; }; + D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F32A6732B7001F4053 /* GradientFollowButton.swift */; }; + D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09652A0AE62100943473 /* AlbyButton.swift */; }; + D73E5ED72C6A97F4007EB227 /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; }; + D73E5ED82C6A97F4007EB227 /* DamusVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2929DDF54400516EAC /* DamusVideoPlayer.swift */; }; + D73E5ED92C6A97F4007EB227 /* DamusVideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */; }; + D73E5EDA2C6A97F4007EB227 /* VideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFE2AA76A0900DFEC1F /* VideoController.swift */; }; + D73E5EDB2C6A97F4007EB227 /* DamusAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFA2AA6C06600DFEC1F /* DamusAVPlayerView.swift */; }; + D73E5EDC2C6A97F4007EB227 /* ReactionsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */; }; + D73E5EDD2C6A97F4007EB227 /* NotificationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */; }; + D73E5EDE2C6A97F4007EB227 /* AppearanceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */; }; + D73E5EDF2C6A97F4007EB227 /* KeySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */; }; + D73E5EE02C6A97F4007EB227 /* ZapSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */; }; + D73E5EE12C6A97F4007EB227 /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */; }; + D73E5EE22C6A97F4007EB227 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; }; + D73E5EE32C6A97F4007EB227 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */; }; + D73E5EE42C6A97F4007EB227 /* FirstAidSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */; }; + D73E5EE52C6A97F4007EB227 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; }; + D73E5EE72C6A97F4007EB227 /* ProfilePicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfilePicImageView.swift */; }; + D73E5EE82C6A97F4007EB227 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */; }; + D73E5EE92C6A97F4007EB227 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFD502E2A2DA45800A229DB /* MediaView.swift */; }; + D73E5EEA2C6A97F4007EB227 /* PurpleViewPrimitives.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C552B76F8E600C59298 /* PurpleViewPrimitives.swift */; }; + D73E5EEB2C6A97F4007EB227 /* MarketingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C572B76FC8400C59298 /* MarketingContentView.swift */; }; + D73E5EEC2C6A97F4007EB227 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C592B76FD5100C59298 /* LogoView.swift */; }; + D73E5EED2C6A97F4007EB227 /* IAPProductStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5B2B77016700C59298 /* IAPProductStateView.swift */; }; + D73E5EEE2C6A97F4007EB227 /* PurpleBackdrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C463CBE2B960B96008A8C36 /* PurpleBackdrop.swift */; }; + D73E5EEF2C6A97F4007EB227 /* DamusPurpleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F5829C9FD1E008DB934 /* DamusPurpleView.swift */; }; + D73E5EF02C6A97F4007EB227 /* DamusPurpleWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */; }; + D73E5EF12C6A97F4007EB227 /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; }; + D73E5EF22C6A97F4007EB227 /* DamusPurpleURLSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3DF2B538D4200F104C4 /* DamusPurpleURLSheetView.swift */; }; + D73E5EF32C6A97F4007EB227 /* DamusPurpleVerifyNpubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3E12B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift */; }; + D73E5EF42C6A97F4007EB227 /* DamusPurpleAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D724D8262B64B40B00ABE789 /* DamusPurpleAccountView.swift */; }; + D73E5EF52C6A97F4007EB227 /* DamusPurpleNewUserOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */; }; + D73E5EF62C6A97F4007EB227 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; }; + D73E5EF72C6A97F4007EB227 /* PullDownSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9D6D1A2B1D35D7004E5CD9 /* PullDownSearch.swift */; }; + D73E5EF82C6A97F4007EB227 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; }; + D73E5EF92C6A97F4007EB227 /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */; }; + D73E5EFA2C6A97F4007EB227 /* NotificationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */; }; + D73E5EFB2C6A97F4007EB227 /* ProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */; }; + D73E5EFC2C6A97F4007EB227 /* DamusAppNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78CD5972B8990300014D539 /* DamusAppNotificationView.swift */; }; + D73E5EFD2C6A97F4007EB227 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */; }; + D73E5EFE2C6A97F4007EB227 /* (null) in Sources */ = {isa = PBXBuildFile; }; + D73E5EFF2C6A97F4007EB227 /* ZapsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879572996C45300F758CC /* ZapsView.swift */; }; + D73E5F002C6A97F4007EB227 /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; }; + D73E5F012C6A97F4007EB227 /* ZapTypePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */; }; + D73E5F022C6A97F4007EB227 /* ZapUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C5132A4437C10062CAC0 /* ZapUserView.swift */; }; + D73E5F032C6A97F4007EB227 /* ProfileZapLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */; }; + D73E5F042C6A97F4007EB227 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8FC222A41ABA500763C51 /* AboutView.swift */; }; + D73E5F052C6A97F4007EB227 /* ProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */; }; + D73E5F062C6A97F4007EB227 /* ProfilePictureSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */; }; + D73E5F072C6A97F4007EB227 /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; }; + D73E5F082C6A97F4007EB227 /* EditPictureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79C7FAC29D5E9620000F946 /* EditPictureControl.swift */; }; + D73E5F092C6A97F4007EB227 /* ProfilePicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */; }; + D73E5F0A2C6A97F4007EB227 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; }; + D73E5F0B2C6A97F4007EB227 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; }; + D73E5F0C2C6A97F4007EB227 /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; }; + D73E5F0D2C6A97F4007EB227 /* EventProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */; }; + D73E5F0E2C6A97F4007EB227 /* FriendIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */; }; + D73E5F0F2C6A97F4007EB227 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; }; + D73E5F102C6A97F4007EB227 /* ProfileEditButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9B0DF22A65C46800CBDA21 /* ProfileEditButton.swift */; }; + D73E5F112C6A97F4007EB227 /* RelayPaidDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879542996BAB900F758CC /* RelayPaidDetail.swift */; }; + D73E5F122C6A97F4007EB227 /* RelayAuthenticationDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C632B312BFA00A232C0 /* RelayAuthenticationDetail.swift */; }; + D73E5F132C6A97F4007EB227 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; }; + D73E5F142C6A97F4007EB227 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; }; + D73E5F152C6A97F4007EB227 /* RelayNipList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */; }; + D73E5F162C6A97F4007EB227 /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; }; + D73E5F172C6A97F4007EB227 /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; }; + D73E5F182C6A97F4007EB227 /* RelayDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E91298B0F0700AB113A /* RelayDetailView.swift */; }; + D73E5F192C6A97F4007EB227 /* RelayToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794D2996B16A00F758CC /* RelayToggle.swift */; }; + D73E5F1A2C6A97F4007EB227 /* RelayStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */; }; + D73E5F1B2C6A97F4007EB227 /* RelayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879512996B68900F758CC /* RelayType.swift */; }; + D73E5F1C2C6A97F4007EB227 /* SignalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128929E9D10C0006FA5A /* SignalView.swift */; }; + D73E5F1D2C6A97F4007EB227 /* RelayPicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */; }; + D73E5F1E2C6A97F4007EB227 /* UserSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABF52985CD5500D66079 /* UserSearch.swift */; }; + D73E5F202C6A97F4007EB227 /* MuteDurationMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51C1CE92B55A60A00E312A9 /* MuteDurationMenu.swift */; }; + D73E5F212C6A97F4007EB227 /* MutelistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE02981A83900D66079 /* MutelistView.swift */; }; + D73E5F222C6A97F4007EB227 /* HighlightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529E2BD744F60039FFC5 /* HighlightView.swift */; }; + D73E5F232C6A97F4007EB227 /* HighlightDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */; }; + D73E5F242C6A97F4007EB227 /* HighlightLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */; }; + D73E5F252C6A97F4007EB227 /* HighlightEventRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */; }; + D73E5F262C6A97F4007EB227 /* HighlightDraftContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */; }; + D73E5F272C6A97F4007EB227 /* TimeDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927642A290F1A0098A105 /* TimeDot.swift */; }; + D73E5F282C6A97F4007EB227 /* EventTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927622A290EB10098A105 /* EventTop.swift */; }; + D73E5F292C6A97F4007EB227 /* ReplyDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF3297F18B400430951 /* ReplyDescription.swift */; }; + D73E5F2A2C6A97F4007EB227 /* RelativeTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927662A290F8B0098A105 /* RelativeTime.swift */; }; + D73E5F2B2C6A97F4007EB227 /* ReplyPart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276B2A2910D10098A105 /* ReplyPart.swift */; }; + D73E5F2C2C6A97F4007EB227 /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; }; + D73E5F2D2C6A97F4007EB227 /* SelectedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEF297F11C700430951 /* SelectedEventView.swift */; }; + D73E5F2E2C6A97F4007EB227 /* EventBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF5297F1A6A00430951 /* EventBody.swift */; }; + D73E5F302C6A97F4007EB227 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; }; + D73E5F312C6A97F4007EB227 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; }; + D73E5F322C6A97F4007EB227 /* EventMutingContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE6298444FC00D66079 /* EventMutingContainerView.swift */; }; + D73E5F332C6A97F4007EB227 /* ZapEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D52B5298DB4E6001C5831 /* ZapEvent.swift */; }; + D73E5F342C6A97F4007EB227 /* TextEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D52B7298DB5C6001C5831 /* TextEvent.swift */; }; + D73E5F352C6A97F4007EB227 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; }; + D73E5F362C6A97F4007EB227 /* LongformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9275C2A28FF630098A105 /* LongformView.swift */; }; + D73E5F372C6A97F4007EB227 /* LongformPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9275E2A2902B20098A105 /* LongformPreview.swift */; }; + D73E5F382C6A97F4007EB227 /* EventShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927602A290E340098A105 /* EventShell.swift */; }; + D73E5F392C6A97F4007EB227 /* MentionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7870BC02AC4750B0080BA88 /* MentionView.swift */; }; + D73E5F3A2C6A97F4007EB227 /* EventLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */; }; + D73E5F3B2C6A97F4007EB227 /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; }; + D73E5F3C2C6A97F4007EB227 /* RepostedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */; }; + D73E5F3D2C6A97F4007EB227 /* QuoteRepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */; }; + D73E5F3E2C6A97F4007EB227 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88395296F7F8B00DC99E7 /* ReactionView.swift */; }; + D73E5F3F2C6A97F4007EB227 /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; }; + D73E5F402C6A97F5007EB227 /* EventDetailBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */; }; + D73E5F412C6A97F5007EB227 /* ShareAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF72FC129B9142F00124A13 /* ShareAction.swift */; }; + D73E5F422C6A97F5007EB227 /* RepostAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE1398F29F0661A00AC6A0B /* RepostAction.swift */; }; + D73E5F432C6A97F5007EB227 /* ShareActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE1399129F0666100AC6A0B /* ShareActionButton.swift */; }; + D73E5F442C6A97F5007EB227 /* BigButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE1399329F0669900AC6A0B /* BigButton.swift */; }; + D73E5F452C6A97F5007EB227 /* AddRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */; }; + D73E5F462C6A97F5007EB227 /* BlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8728236948006E126D /* BlocksView.swift */; }; + D73E5F472C6A97F5007EB227 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12E29A18EF500E10810 /* BookmarksView.swift */; }; + D73E5F482C6A97F5007EB227 /* CarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8128385570008A31F1 /* CarouselView.swift */; }; + D73E5F492C6A97F5007EB227 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; }; + D73E5F4A2C6A97F5007EB227 /* CreateAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8328385690008A31F1 /* CreateAccountView.swift */; }; + D73E5F4B2C6A97F5007EB227 /* DirectMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */; }; + D73E5F4C2C6A97F5007EB227 /* DMChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F31286E388800040376 /* DMChatView.swift */; }; + D73E5F4D2C6A97F5007EB227 /* DMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F33286F5ACD00040376 /* DMView.swift */; }; + D73E5F4E2C6A97F5007EB227 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; }; + D73E5F4F2C6A97F5007EB227 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; }; + D73E5F502C6A97F5007EB227 /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB82804A2740006080F /* EventView.swift */; }; + D73E5F512C6A97F5007EB227 /* EventDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */; }; + D73E5F522C6A97F5007EB227 /* FollowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79E2833115300E1F516 /* FollowButtonView.swift */; }; + D73E5F532C6A97F5007EB227 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79C2833036D00E1F516 /* FollowingView.swift */; }; + D73E5F542C6A97F5007EB227 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; }; + D73E5F552C6A97F5007EB227 /* QRScanNSECView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADFE73542AD4793100EC7326 /* QRScanNSECView.swift */; }; + D73E5F562C6A97F5007EB227 /* NoteContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8D28236FE4006E126D /* NoteContentView.swift */; }; + D73E5F572C6A97F5007EB227 /* PostButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAC28049CFB0006080F /* PostButton.swift */; }; + D73E5F582C6A97F5007EB227 /* MediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757933929D7AECD007DEAC1 /* MediaPicker.swift */; }; + D73E5F592C6A97F5007EB227 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; }; + D73E5F5A2C6A97F5007EB227 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC7A42836987600E1F516 /* MainTabView.swift */; }; + D73E5F5B2C6A97F5007EB227 /* PubkeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8B28236B92006E126D /* PubkeyView.swift */; }; + D73E5F5C2C6A97F5007EB227 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; }; + D73E5F5D2C6A97F5007EB227 /* ParticipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParticipantsView.swift */; }; + D73E5F5E2C6A97F5007EB227 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; }; + D73E5F5F2C6A97F5007EB227 /* SearchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */; }; + D73E5F602C6A97F5007EB227 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */; }; + D73E5F612C6A97F5007EB227 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; }; + D73E5F622C6A97F5007EB227 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; }; + D73E5F642C6A97F5007EB227 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; }; + D73E5F652C6A97F5007EB227 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; }; + D73E5F662C6A97F5007EB227 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; }; + D73E5F682C6A97F5007EB227 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; }; + D73E5F692C6A97F5007EB227 /* ReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838E296F781C00DC99E7 /* ReactionsView.swift */; }; + D73E5F6A2C6A97F5007EB227 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABD529817F5B00D66079 /* ReportView.swift */; }; + D73E5F6C2C6A97F5007EB227 /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; }; + D73E5F6D2C6A97F5007EB227 /* Launch.storyboard in Sources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; }; + D73E5F6F2C6A97F5007EB227 /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643EA5C7296B764E005081BB /* RelayFilterView.swift */; }; + D73E5F732C6A9885007EB227 /* TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C687C262A6039500092C550 /* TestData.swift */; }; + D73E5F742C6A9890007EB227 /* damusApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE627F7A08100C66700 /* damusApp.swift */; }; + D73E5F762C6A997E007EB227 /* EmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = D73E5F752C6A997E007EB227 /* EmojiPicker */; }; + D73E5F782C6A9A5C007EB227 /* NdbNote+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D798D2272B085CDA00234419 /* NdbNote+.swift */; }; + D73E5F792C6A9C4C007EB227 /* HomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C63334F283D40E500B1C9C3 /* HomeModel.swift */; }; + D73E5F7A2C6A9C55007EB227 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */; }; + D73E5F7B2C6A9D0F007EB227 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; }; + D73E5F7C2C6A9D4F007EB227 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE827F7A08100C66700 /* ContentView.swift */; }; + D73E5F7F2C6AA066007EB227 /* DamusAliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73E5F7E2C6AA066007EB227 /* DamusAliases.swift */; }; + D73E5F812C6AA07A007EB227 /* HighlighterExtensionAliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73E5F802C6AA07A007EB227 /* HighlighterExtensionAliases.swift */; }; + D73E5F852C6AA628007EB227 /* LoadScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F242A547D2000027FD5 /* LoadScript.swift */; }; + D73E5F862C6AA62F007EB227 /* ChatroomThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5D2BD0A56A002F2F9B /* ChatroomThreadView.swift */; }; + D73E5F872C6AA639007EB227 /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; }; + D73E5F882C6AA661007EB227 /* NostrScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C19AE4C2A5CEF7C00C90DB7 /* NostrScript.swift */; }; + D73E5F892C6AA670007EB227 /* BuilderEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */; }; + D73E5F8A2C6AA69C007EB227 /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; }; + D73E5F8B2C6AA6A2007EB227 /* UserStatusSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5E54052A9671F800FF6E60 /* UserStatusSheet.swift */; }; + D73E5F8C2C6AA6A7007EB227 /* ProfileActionSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */; }; + D73E5F8D2C6AA6D7007EB227 /* AddMuteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51C1CE82B55A60A00E312A9 /* AddMuteItemView.swift */; }; + D73E5F8E2C6AA6F3007EB227 /* InvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */; }; + D73E5F8F2C6AA70A007EB227 /* ChatEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */; }; + D73E5F902C6AA715007EB227 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; }; + D73E5F912C6AA71B007EB227 /* InputDismissKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */; }; + D73E5F922C6AA720007EB227 /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; }; + D73E5F932C6AA743007EB227 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC7A02835A81400E1F516 /* SetupView.swift */; }; + D73E5F942C6AA74D007EB227 /* EULAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE42981EE0C00D66079 /* EULAView.swift */; }; + D73E5F952C6AA753007EB227 /* FullScreenCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* FullScreenCarouselView.swift */; }; + D73E5F962C6AA7B0007EB227 /* ConnectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095C2A098C5D00943473 /* ConnectWalletView.swift */; }; + D73E5F972C6AA7B7007EB227 /* SuggestedHashtagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D783A63E2AD4E53D00658DDA /* SuggestedHashtagsView.swift */; }; + D73E5F982C6AA847007EB227 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */; }; + D73E5F992C6AA864007EB227 /* InvoicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */; }; + D73E5F9B2C6AA8B0007EB227 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D73E5F9A2C6AA8B0007EB227 /* Kingfisher */; }; + D73E5F9D2C6AA8E3007EB227 /* SwipeActions in Frameworks */ = {isa = PBXBuildFile; productRef = D73E5F9C2C6AA8E3007EB227 /* SwipeActions */; }; + D73E5F9E2C6AA9F7007EB227 /* nostrscript.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */; }; D74AAFC22B153395006CF0F4 /* HeadlessDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */; }; D74AAFC32B153395006CF0F4 /* HeadlessDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */; }; D74AAFC52B1538DF006CF0F4 /* NotificationExtensionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFC42B1538DE006CF0F4 /* NotificationExtensionState.swift */; }; @@ -698,6 +1176,13 @@ remoteGlobalIDString = 4CE6DEE227F7A08100C66700; remoteInfo = damus; }; + D703D7232C66E47100A400EA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4CE6DEDB27F7A08100C66700 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D703D7162C66E47100A400EA; + remoteInfo = "highlighter action extension"; + }; D79C4C192AFEB061003A41B4 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4CE6DEDB27F7A08100C66700 /* Project object */; @@ -714,6 +1199,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + D703D7252C66E47100A400EA /* HighlighterActionExtension.appex in Embed Foundation Extensions */, D79C4C1B2AFEB061003A41B4 /* DamusNotificationService.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; @@ -1395,6 +1881,13 @@ BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = ""; }; BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = ""; }; D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + D703D7172C66E47100A400EA /* HighlighterActionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = HighlighterActionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + D703D7182C66E47100A400EA /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; + D703D71B2C66E47100A400EA /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; + D703D71D2C66E47100A400EA /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; }; + D703D7222C66E47100A400EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D703D7262C66E47100A400EA /* highlighter action extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "highlighter action extension.entitlements"; sourceTree = ""; }; + D703D72A2C66F29500A400EA /* getSelection.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = getSelection.js; sourceTree = ""; }; D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFormatter.swift; sourceTree = ""; }; D7100C552B76F8E600C59298 /* PurpleViewPrimitives.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleViewPrimitives.swift; sourceTree = ""; }; D7100C572B76FC8400C59298 /* MarketingContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketingContentView.swift; sourceTree = ""; }; @@ -1417,6 +1910,8 @@ D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleTranslationSetupView.swift; sourceTree = ""; }; D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNewUserOnboardingView.swift; sourceTree = ""; }; D7373BA92B68A65A00F7783D /* PurpleAccountUpdateNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleAccountUpdateNotify.swift; sourceTree = ""; }; + D73E5F7E2C6AA066007EB227 /* DamusAliases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusAliases.swift; sourceTree = ""; }; + D73E5F802C6AA07A007EB227 /* HighlighterExtensionAliases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlighterExtensionAliases.swift; sourceTree = ""; }; D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessDamusState.swift; sourceTree = ""; }; D74AAFC42B1538DE006CF0F4 /* NotificationExtensionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationExtensionState.swift; sourceTree = ""; }; D74AAFCB2B155D07006CF0F4 /* MakeZapRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeZapRequest.swift; sourceTree = ""; }; @@ -1524,6 +2019,19 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D703D7142C66E47100A400EA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D703D7AF2C670FB700A400EA /* MarkdownUI in Frameworks */, + D73E5F9D2C6AA8E3007EB227 /* SwipeActions in Frameworks */, + D73E5F762C6A997E007EB227 /* EmojiPicker in Frameworks */, + D703D7192C66E47100A400EA /* UniformTypeIdentifiers.framework in Frameworks */, + D703D7492C6709B100A400EA /* secp256k1 in Frameworks */, + D73E5F9B2C6AA8B0007EB227 /* Kingfisher in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D79C4C112AFEB061003A41B4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2168,6 +2676,7 @@ 4C7FF7D628233637009601DB /* Util */ = { isa = PBXGroup; children = ( + D73E5F7E2C6AA066007EB227 /* DamusAliases.swift */, E04A37C52B544F090029650D /* URIParsing.swift */, 4C1D4FB02A7958E60024F453 /* VersionInfo.swift */, 4C7D09612A098D0E00943473 /* WalletConnect.swift */, @@ -2501,6 +3010,7 @@ 4CE6DEF627F7A08200C66700 /* damusTests */, 4CE6DF0027F7A08200C66700 /* damusUITests */, D79C4C152AFEB061003A41B4 /* DamusNotificationService */, + D703D71A2C66E47100A400EA /* highlighter action extension */, 4CE6DEE427F7A08100C66700 /* Products */, 4CEE2AE62804F57B00AB5EEF /* Frameworks */, ); @@ -2515,6 +3025,7 @@ 4CE6DEF327F7A08200C66700 /* damusTests.xctest */, 4CE6DEFD27F7A08200C66700 /* damusUITests.xctest */, D79C4C142AFEB061003A41B4 /* DamusNotificationService.appex */, + D703D7172C66E47100A400EA /* HighlighterActionExtension.appex */, ); name = Products; sourceTree = ""; @@ -2659,6 +3170,7 @@ isa = PBXGroup; children = ( 4CEE2AE72804F57C00AB5EEF /* libsecp256k1.a */, + D703D7182C66E47100A400EA /* UniformTypeIdentifiers.framework */, ); name = Frameworks; sourceTree = ""; @@ -2763,6 +3275,19 @@ path = Camera; sourceTree = ""; }; + D703D71A2C66E47100A400EA /* highlighter action extension */ = { + isa = PBXGroup; + children = ( + D73E5F802C6AA07A007EB227 /* HighlighterExtensionAliases.swift */, + D703D7262C66E47100A400EA /* highlighter action extension.entitlements */, + D703D71B2C66E47100A400EA /* Media.xcassets */, + D703D71D2C66E47100A400EA /* ActionViewController.swift */, + D703D7222C66E47100A400EA /* Info.plist */, + D703D72A2C66F29500A400EA /* getSelection.js */, + ); + path = "highlighter action extension"; + sourceTree = ""; + }; D7100C542B76F8C200C59298 /* Detail */ = { isa = PBXGroup; children = ( @@ -2891,6 +3416,7 @@ ); dependencies = ( D79C4C1A2AFEB061003A41B4 /* PBXTargetDependency */, + D703D7242C66E47100A400EA /* PBXTargetDependency */, ); name = damus; packageProductDependencies = ( @@ -2944,6 +3470,31 @@ productReference = 4CE6DEFD27F7A08200C66700 /* damusUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + D703D7162C66E47100A400EA /* HighlighterActionExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = D703D7272C66E47100A400EA /* Build configuration list for PBXNativeTarget "HighlighterActionExtension" */; + buildPhases = ( + D703D7132C66E47100A400EA /* Sources */, + D703D7142C66E47100A400EA /* Frameworks */, + D703D7152C66E47100A400EA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D703D7AD2C670FA700A400EA /* PBXTargetDependency */, + ); + name = HighlighterActionExtension; + packageProductDependencies = ( + D703D7482C6709B100A400EA /* secp256k1 */, + D703D7AE2C670FB700A400EA /* MarkdownUI */, + D73E5F752C6A997E007EB227 /* EmojiPicker */, + D73E5F9A2C6AA8B0007EB227 /* Kingfisher */, + D73E5F9C2C6AA8E3007EB227 /* SwipeActions */, + ); + productName = "highlighter action extension"; + productReference = D703D7172C66E47100A400EA /* HighlighterActionExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; D79C4C132AFEB061003A41B4 /* DamusNotificationService */ = { isa = PBXNativeTarget; buildConfigurationList = D79C4C202AFEB061003A41B4 /* Build configuration list for PBXNativeTarget "DamusNotificationService" */; @@ -2973,7 +3524,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1500; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1520; TargetAttributes = { 4CE6DEE227F7A08100C66700 = { @@ -2988,6 +3539,9 @@ CreatedOnToolsVersion = 13.3; TestTargetID = 4CE6DEE227F7A08100C66700; }; + D703D7162C66E47100A400EA = { + CreatedOnToolsVersion = 15.4; + }; D79C4C132AFEB061003A41B4 = { CreatedOnToolsVersion = 15.0.1; }; @@ -3049,6 +3603,7 @@ 4CE6DEF227F7A08200C66700 /* damusTests */, 4CE6DEFC27F7A08200C66700 /* damusUITests */, D79C4C132AFEB061003A41B4 /* DamusNotificationService */, + D703D7162C66E47100A400EA /* HighlighterActionExtension */, ); }; /* End PBXProject section */ @@ -3089,6 +3644,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D703D7152C66E47100A400EA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D73E5F982C6AA847007EB227 /* Assets.xcassets in Resources */, + D703D72B2C66F29500A400EA /* getSelection.js in Resources */, + D703D71C2C66E47100A400EA /* Media.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D79C4C122AFEB061003A41B4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3397,6 +3962,7 @@ 4C011B5F2BD0A56A002F2F9B /* ChatroomThreadView.swift in Sources */, 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */, 4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */, + D73E5F7F2C6AA066007EB227 /* DamusAliases.swift in Sources */, 4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */, 4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */, 5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */, @@ -3659,6 +4225,481 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D703D7132C66E47100A400EA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D73E5E202C6A97F4007EB227 /* AttachedWalletNotify.swift in Sources */, + D73E5E212C6A97F4007EB227 /* DisplayTabBarNotify.swift in Sources */, + D73E5E222C6A97F4007EB227 /* BroadcastNotify.swift in Sources */, + D73E5E232C6A97F4007EB227 /* ComposeNotify.swift in Sources */, + D73E5E242C6A97F4007EB227 /* FollowedNotify.swift in Sources */, + D73E5E252C6A97F4007EB227 /* FollowNotify.swift in Sources */, + D73E5E262C6A97F4007EB227 /* LikedNotify.swift in Sources */, + D73E5E272C6A97F4007EB227 /* LocalNotificationNotify.swift in Sources */, + D73E5F8B2C6AA6A2007EB227 /* UserStatusSheet.swift in Sources */, + D73E5E282C6A97F4007EB227 /* LoginNotify.swift in Sources */, + D73E5E292C6A97F4007EB227 /* LogoutNotify.swift in Sources */, + D73E5E2A2C6A97F4007EB227 /* OnlyZapsNotify.swift in Sources */, + D73E5E2B2C6A97F4007EB227 /* PostNotify.swift in Sources */, + D73E5E2C2C6A97F4007EB227 /* PresentSheetNotify.swift in Sources */, + D73E5E2D2C6A97F4007EB227 /* ProfileUpdatedNotify.swift in Sources */, + D73E5E2E2C6A97F4007EB227 /* ReportNotify.swift in Sources */, + D73E5E2F2C6A97F4007EB227 /* ScrollToTopNotify.swift in Sources */, + D73E5E302C6A97F4007EB227 /* SwitchedTimelineNotify.swift in Sources */, + D73E5E312C6A97F4007EB227 /* UnfollowedNotify.swift in Sources */, + D73E5E322C6A97F4007EB227 /* UnfollowNotify.swift in Sources */, + D73E5E332C6A97F4007EB227 /* ZappingNotify.swift in Sources */, + D73E5F8E2C6AA6F3007EB227 /* InvoiceView.swift in Sources */, + D73E5F7C2C6A9D4F007EB227 /* ContentView.swift in Sources */, + D73E5E342C6A97F4007EB227 /* MuteNotify.swift in Sources */, + D73E5E352C6A97F4007EB227 /* RelaysChangedNotify.swift in Sources */, + D73E5E362C6A97F4007EB227 /* MuteThreadNotify.swift in Sources */, + D73E5E372C6A97F4007EB227 /* ReconnectRelaysNotify.swift in Sources */, + D73E5E382C6A97F4007EB227 /* PurpleAccountUpdateNotify.swift in Sources */, + D73E5E392C6A97F4007EB227 /* DamusDuration.swift in Sources */, + D73E5E3A2C6A97F4007EB227 /* SwipeToDismiss.swift in Sources */, + D73E5E3B2C6A97F4007EB227 /* MusicController.swift in Sources */, + D73E5E3C2C6A97F4007EB227 /* UserStatusView.swift in Sources */, + D73E5E3E2C6A97F4007EB227 /* SearchHeaderView.swift in Sources */, + D73E5E3F2C6A97F4007EB227 /* DamusGradient.swift in Sources */, + D73E5E402C6A97F4007EB227 /* AlbyGradient.swift in Sources */, + D73E5E412C6A97F4007EB227 /* GoldSupportGradient.swift in Sources */, + D73E5E422C6A97F4007EB227 /* PinkGradient.swift in Sources */, + D73E5E432C6A97F4007EB227 /* GrayGradient.swift in Sources */, + D73E5E442C6A97F4007EB227 /* DamusLogoGradient.swift in Sources */, + D73E5E452C6A97F4007EB227 /* DamusBackground.swift in Sources */, + D73E5E462C6A97F4007EB227 /* DamusLightGradient.swift in Sources */, + D73E5E472C6A97F4007EB227 /* MutinyGradient.swift in Sources */, + D73E5E482C6A97F4007EB227 /* Shimmer.swift in Sources */, + D73E5E492C6A97F4007EB227 /* EndBlock.swift in Sources */, + D73E5E4D2C6A97F4007EB227 /* NIP05Badge.swift in Sources */, + D73E5E4E2C6A97F4007EB227 /* Reposted.swift in Sources */, + D73E5E4F2C6A97F4007EB227 /* WebsiteLink.swift in Sources */, + D73E5E502C6A97F4007EB227 /* Highlight.swift in Sources */, + D73E5E512C6A97F4007EB227 /* CustomPicker.swift in Sources */, + D73E5E522C6A97F4007EB227 /* UserView.swift in Sources */, + D73E5E532C6A97F4007EB227 /* ZoomableScrollView.swift in Sources */, + D73E5E542C6A97F4007EB227 /* NoteZapButton.swift in Sources */, + D73E5E552C6A97F4007EB227 /* TranslateView.swift in Sources */, + D73E5E562C6A97F4007EB227 /* SelectableText.swift in Sources */, + D73E5E572C6A97F4007EB227 /* DamusColors.swift in Sources */, + D73E5E582C6A97F4007EB227 /* ThiccDivider.swift in Sources */, + D73E5E592C6A97F4007EB227 /* IconLabel.swift in Sources */, + D73E5E5A2C6A97F4007EB227 /* TruncatedText.swift in Sources */, + D73E5E5B2C6A97F4007EB227 /* SupporterBadge.swift in Sources */, + D73E5E5C2C6A97F4007EB227 /* GradientButtonStyle.swift in Sources */, + D73E5E5D2C6A97F4007EB227 /* NeutralButtonStyle.swift in Sources */, + D73E5E5E2C6A97F4007EB227 /* URIParsing.swift in Sources */, + D73E5E5F2C6A97F4007EB227 /* VersionInfo.swift in Sources */, + D73E5E602C6A97F4007EB227 /* ImageMetadata.swift in Sources */, + D73E5E612C6A97F4007EB227 /* ImageProcessing.swift in Sources */, + D73E5E622C6A97F4007EB227 /* BlurHashEncode.swift in Sources */, + D73E5E632C6A97F4007EB227 /* BlurHashDecode.swift in Sources */, + D73E5F952C6AA753007EB227 /* FullScreenCarouselView.swift in Sources */, + D73E5E642C6A97F4007EB227 /* PostBox.swift in Sources */, + D73E5E652C6A97F4007EB227 /* KFOptionSetter+.swift in Sources */, + D73E5E662C6A97F4007EB227 /* FillAndStroke.swift in Sources */, + D73E5E672C6A97F4007EB227 /* Array.swift in Sources */, + D73E5E682C6A97F4007EB227 /* VectorMath.swift in Sources */, + D73E5E692C6A97F4007EB227 /* RelayBootstrap.swift in Sources */, + D73E5E6A2C6A97F4007EB227 /* RelayModel.swift in Sources */, + D73E5E6B2C6A97F4007EB227 /* AnyCodable.swift in Sources */, + D73E5E6C2C6A97F4007EB227 /* AnyDecodable.swift in Sources */, + D73E5E6D2C6A97F4007EB227 /* AnyEncodable.swift in Sources */, + D73E5F782C6A9A5C007EB227 /* NdbNote+.swift in Sources */, + D73E5E6E2C6A97F4007EB227 /* NIPURLBuilder.swift in Sources */, + D73E5E6F2C6A97F4007EB227 /* TimeAgo.swift in Sources */, + D73E5E702C6A97F4007EB227 /* Parser.swift in Sources */, + D73E5E722C6A97F4007EB227 /* LinkView.swift in Sources */, + D73E5F922C6AA720007EB227 /* QRCodeView.swift in Sources */, + D73E5E742C6A97F4007EB227 /* Lists.swift in Sources */, + D73E5E752C6A97F4007EB227 /* CoreSVG.swift in Sources */, + D73E5E762C6A97F4007EB227 /* AccountDeletion.swift in Sources */, + D73E5E772C6A97F4007EB227 /* Translator.swift in Sources */, + D73E5E782C6A97F4007EB227 /* Debouncer.swift in Sources */, + D73E5E792C6A97F4007EB227 /* EventHolder.swift in Sources */, + D73E5E7A2C6A97F4007EB227 /* EventCache.swift in Sources */, + D73E5E7B2C6A97F4007EB227 /* DebouncedOnChange.swift in Sources */, + D73E5E7C2C6A97F4007EB227 /* ReplyCounter.swift in Sources */, + D73E5E7D2C6A97F4007EB227 /* CompatibleAttribute.swift in Sources */, + D73E5E7E2C6A97F4007EB227 /* Hashtags.swift in Sources */, + D73E5E7F2C6A97F4007EB227 /* LocalNotification.swift in Sources */, + D73E5E802C6A97F4007EB227 /* CredentialHandler.swift in Sources */, + D73E5E812C6A97F4007EB227 /* KeyboardVisible.swift in Sources */, + D73E5E832C6A97F4007EB227 /* AVPlayer+Additions.swift in Sources */, + D73E5E842C6A97F4007EB227 /* Zaps+.swift in Sources */, + D73E5E852C6A97F4007EB227 /* WalletConnect+.swift in Sources */, + D73E5E862C6A97F4007EB227 /* DamusPurpleNotificationManagement.swift in Sources */, + D73E5E872C6A97F4007EB227 /* DamusPurple.swift in Sources */, + D73E5F992C6AA864007EB227 /* InvoicesView.swift in Sources */, + D73E5E882C6A97F4007EB227 /* StoreObserver.swift in Sources */, + D73E5E892C6A97F4007EB227 /* DamusPurpleURL.swift in Sources */, + D73E5E8A2C6A97F4007EB227 /* PurpleStoreKitManager.swift in Sources */, + D73E5E8D2C6A97F4007EB227 /* CameraService+Extensions.swift in Sources */, + D73E5E8E2C6A97F4007EB227 /* ImageResizer.swift in Sources */, + D73E5E8F2C6A97F4007EB227 /* PhotoCaptureProcessor.swift in Sources */, + D773BC602C6D538500349F0A /* CommentItem.swift in Sources */, + D73E5E902C6A97F4007EB227 /* VideoCaptureProcessor.swift in Sources */, + D73E5E912C6A97F4007EB227 /* CustomizeZapModel.swift in Sources */, + D73E5E922C6A97F4007EB227 /* EventGroup.swift in Sources */, + D73E5E932C6A97F4007EB227 /* ZapGroup.swift in Sources */, + D73E5E942C6A97F4007EB227 /* NotificationStatusModel.swift in Sources */, + D73E5E952C6A97F4007EB227 /* ThreadModel.swift in Sources */, + D73E5E962C6A97F4007EB227 /* ReplyMap.swift in Sources */, + D73E5E972C6A97F4007EB227 /* ProfileModel.swift in Sources */, + D73E5E982C6A97F4007EB227 /* ActionBarModel.swift in Sources */, + D73E5E992C6A97F4007EB227 /* Liked.swift in Sources */, + D73E5E9A2C6A97F4007EB227 /* ProfileUpdate.swift in Sources */, + D73E5E9B2C6A97F4007EB227 /* PostBlock.swift in Sources */, + D73E5E9C2C6A97F4007EB227 /* Reply.swift in Sources */, + D73E5E9D2C6A97F4007EB227 /* SearchModel.swift in Sources */, + D73E5E9E2C6A97F4007EB227 /* NostrFilter+Hashable.swift in Sources */, + D73E5F912C6AA71B007EB227 /* InputDismissKeyboard.swift in Sources */, + D73E5E9F2C6A97F4007EB227 /* CreateAccountModel.swift in Sources */, + D73E5EA12C6A97F4007EB227 /* SignalModel.swift in Sources */, + D73E5EA22C6A97F4007EB227 /* FollowTarget.swift in Sources */, + D73E5EA32C6A97F4007EB227 /* BookmarksManager.swift in Sources */, + D73E5EA42C6A97F4007EB227 /* EventsModel.swift in Sources */, + D73E5EA52C6A97F4007EB227 /* FollowingModel.swift in Sources */, + D73E5EA62C6A97F4007EB227 /* FollowersModel.swift in Sources */, + D73E5EA72C6A97F4007EB227 /* SearchHomeModel.swift in Sources */, + D73E5EA82C6A97F4007EB227 /* DirectMessageModel.swift in Sources */, + D73E5EA92C6A97F4007EB227 /* Report.swift in Sources */, + D73E5EAA2C6A97F4007EB227 /* ZapsModel.swift in Sources */, + D73E5EAB2C6A97F4007EB227 /* DraftsModel.swift in Sources */, + D73E5F932C6AA743007EB227 /* SetupView.swift in Sources */, + D73E5EAC2C6A97F4007EB227 /* NotificationsModel.swift in Sources */, + D73E5F902C6AA715007EB227 /* Theme.swift in Sources */, + D73E5EAD2C6A97F4007EB227 /* MutedThreadsManager.swift in Sources */, + D73E5EAE2C6A97F4007EB227 /* WalletModel.swift in Sources */, + D73E5EAF2C6A97F4007EB227 /* ZapButtonModel.swift in Sources */, + D73E5EB02C6A97F4007EB227 /* ContentFilters.swift in Sources */, + D73E5EB12C6A97F4007EB227 /* DamusCacheManager.swift in Sources */, + D73E5EB22C6A97F4007EB227 /* NotificationsManager.swift in Sources */, + D73E5EB32C6A97F4007EB227 /* Contacts+.swift in Sources */, + D73E5EB42C6A97F4007EB227 /* NoteContent.swift in Sources */, + D73E5EB52C6A97F4007EB227 /* LongformEvent.swift in Sources */, + D73E5EB62C6A97F4007EB227 /* PushNotificationClient.swift in Sources */, + D73E5EB72C6A97F4007EB227 /* HighlightEvent.swift in Sources */, + D73E5EB82C6A97F4007EB227 /* RelayConnection.swift in Sources */, + D73E5EB92C6A97F4007EB227 /* RelayLog.swift in Sources */, + D73E5EBA2C6A97F4007EB227 /* NostrFilter.swift in Sources */, + D73E5EBB2C6A97F4007EB227 /* Nip98HTTPAuth.swift in Sources */, + D73E5EBC2C6A97F4007EB227 /* Relay.swift in Sources */, + D73E5EBD2C6A97F4007EB227 /* NostrRequest.swift in Sources */, + D73E5EBE2C6A97F4007EB227 /* NostrLink.swift in Sources */, + D73E5EBF2C6A97F4007EB227 /* WebSocket.swift in Sources */, + D73E5F812C6AA07A007EB227 /* HighlighterExtensionAliases.swift in Sources */, + D73E5EC02C6A97F4007EB227 /* NostrEvent+.swift in Sources */, + D73E5EC12C6A97F4007EB227 /* NIP98AuthenticatedRequest.swift in Sources */, + D73E5EC22C6A97F4007EB227 /* NostrAuth.swift in Sources */, + D73E5EC42C6A97F4007EB227 /* ReplyQuoteView.swift in Sources */, + D73E5EC62C6A97F4007EB227 /* ChatBubbleView.swift in Sources */, + D73E5EC72C6A97F4007EB227 /* VisibilityTracker.swift in Sources */, + D73E5EC82C6A97F4007EB227 /* CameraPreview.swift in Sources */, + D73E5EC92C6A97F4007EB227 /* CameraController.swift in Sources */, + D73E5ECA2C6A97F4007EB227 /* OnboardingSuggestionsView.swift in Sources */, + D73E5ECB2C6A97F4007EB227 /* SuggestedUserView.swift in Sources */, + D73E5ECC2C6A97F4007EB227 /* SuggestedUsersViewModel.swift in Sources */, + D73E5ECE2C6A97F4007EB227 /* CodeScanner.swift in Sources */, + D73E5ECF2C6A97F4007EB227 /* ScannerCoordinator.swift in Sources */, + D73E5ED02C6A97F4007EB227 /* ScannerViewController.swift in Sources */, + D73E5ED22C6A97F4007EB227 /* WalletView.swift in Sources */, + D73E5ED32C6A97F4007EB227 /* NWCScannerView.swift in Sources */, + D73E5ED42C6A97F4007EB227 /* FriendsButton.swift in Sources */, + D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */, + D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */, + D73E5ED72C6A97F4007EB227 /* MutinyButton.swift in Sources */, + D73E5ED82C6A97F4007EB227 /* DamusVideoPlayer.swift in Sources */, + D73E5ED92C6A97F4007EB227 /* DamusVideoPlayerViewModel.swift in Sources */, + D73E5EDA2C6A97F4007EB227 /* VideoController.swift in Sources */, + D73E5EDB2C6A97F4007EB227 /* DamusAVPlayerView.swift in Sources */, + D73E5EDC2C6A97F4007EB227 /* ReactionsSettingsView.swift in Sources */, + D73E5EDD2C6A97F4007EB227 /* NotificationSettingsView.swift in Sources */, + D73E5EDE2C6A97F4007EB227 /* AppearanceSettingsView.swift in Sources */, + D73E5EDF2C6A97F4007EB227 /* KeySettingsView.swift in Sources */, + D73E5EE02C6A97F4007EB227 /* ZapSettingsView.swift in Sources */, + D73E5F792C6A9C4C007EB227 /* HomeModel.swift in Sources */, + D73E5EE12C6A97F4007EB227 /* TranslationSettingsView.swift in Sources */, + D73E5EE22C6A97F4007EB227 /* SearchSettingsView.swift in Sources */, + D73E5EE32C6A97F4007EB227 /* DeveloperSettingsView.swift in Sources */, + D73E5EE42C6A97F4007EB227 /* FirstAidSettingsView.swift in Sources */, + D73E5EE52C6A97F4007EB227 /* ImageContextMenuModifier.swift in Sources */, + D73E5EE72C6A97F4007EB227 /* ProfilePicImageView.swift in Sources */, + D73E5EE82C6A97F4007EB227 /* ImageContainerView.swift in Sources */, + D73E5EE92C6A97F4007EB227 /* MediaView.swift in Sources */, + D73E5EEA2C6A97F4007EB227 /* PurpleViewPrimitives.swift in Sources */, + D73E5F8C2C6AA6A7007EB227 /* ProfileActionSheetView.swift in Sources */, + D73E5EEB2C6A97F4007EB227 /* MarketingContentView.swift in Sources */, + D73E5EEC2C6A97F4007EB227 /* LogoView.swift in Sources */, + D73E5EED2C6A97F4007EB227 /* IAPProductStateView.swift in Sources */, + D73E5EEE2C6A97F4007EB227 /* PurpleBackdrop.swift in Sources */, + D73E5EEF2C6A97F4007EB227 /* DamusPurpleView.swift in Sources */, + D73E5EF02C6A97F4007EB227 /* DamusPurpleWelcomeView.swift in Sources */, + D73E5EF12C6A97F4007EB227 /* DamusPurpleTranslationSetupView.swift in Sources */, + D73E5EF22C6A97F4007EB227 /* DamusPurpleURLSheetView.swift in Sources */, + D73E5EF32C6A97F4007EB227 /* DamusPurpleVerifyNpubView.swift in Sources */, + D73E5EF42C6A97F4007EB227 /* DamusPurpleAccountView.swift in Sources */, + D73E5EF52C6A97F4007EB227 /* DamusPurpleNewUserOnboardingView.swift in Sources */, + D73E5EF62C6A97F4007EB227 /* SearchingEventView.swift in Sources */, + D73E5EF72C6A97F4007EB227 /* PullDownSearch.swift in Sources */, + D73E5EF82C6A97F4007EB227 /* NotificationsView.swift in Sources */, + D73E5EF92C6A97F4007EB227 /* EventGroupView.swift in Sources */, + D73E5EFA2C6A97F4007EB227 /* NotificationItemView.swift in Sources */, + D73E5EFB2C6A97F4007EB227 /* ProfilePicturesView.swift in Sources */, + D73E5EFC2C6A97F4007EB227 /* DamusAppNotificationView.swift in Sources */, + D73E5EFD2C6A97F4007EB227 /* InnerTimelineView.swift in Sources */, + D73E5EFE2C6A97F4007EB227 /* (null) in Sources */, + D73E5EFF2C6A97F4007EB227 /* ZapsView.swift in Sources */, + D73E5F002C6A97F4007EB227 /* CustomizeZapView.swift in Sources */, + D73E5F012C6A97F4007EB227 /* ZapTypePicker.swift in Sources */, + D73E5F022C6A97F4007EB227 /* ZapUserView.swift in Sources */, + D73E5F032C6A97F4007EB227 /* ProfileZapLinkView.swift in Sources */, + D73E5F042C6A97F4007EB227 /* AboutView.swift in Sources */, + D73E5F052C6A97F4007EB227 /* ProfileName.swift in Sources */, + D73E5F062C6A97F4007EB227 /* ProfilePictureSelector.swift in Sources */, + D73E5F8F2C6AA70A007EB227 /* ChatEventView.swift in Sources */, + D73E5F072C6A97F4007EB227 /* EditMetadataView.swift in Sources */, + D73E5F862C6AA62F007EB227 /* ChatroomThreadView.swift in Sources */, + D73E5F082C6A97F4007EB227 /* EditPictureControl.swift in Sources */, + D73E5F092C6A97F4007EB227 /* ProfilePicView.swift in Sources */, + D73E5F0A2C6A97F4007EB227 /* ProfileView.swift in Sources */, + D73E5F0B2C6A97F4007EB227 /* ProfileNameView.swift in Sources */, + D73E5F0C2C6A97F4007EB227 /* MaybeAnonPfpView.swift in Sources */, + D73E5F0D2C6A97F4007EB227 /* EventProfileName.swift in Sources */, + D73E5F0E2C6A97F4007EB227 /* FriendIcon.swift in Sources */, + D73E5F0F2C6A97F4007EB227 /* CondensedProfilePicturesView.swift in Sources */, + D73E5F102C6A97F4007EB227 /* ProfileEditButton.swift in Sources */, + D73E5F112C6A97F4007EB227 /* RelayPaidDetail.swift in Sources */, + D73E5F122C6A97F4007EB227 /* RelayAuthenticationDetail.swift in Sources */, + D73E5F132C6A97F4007EB227 /* RelaySoftwareDetail.swift in Sources */, + D73E5F142C6A97F4007EB227 /* RelayAdminDetail.swift in Sources */, + D73E5F152C6A97F4007EB227 /* RelayNipList.swift in Sources */, + D73E5F162C6A97F4007EB227 /* RelayView.swift in Sources */, + D73E5F172C6A97F4007EB227 /* RelayConfigView.swift in Sources */, + D73E5F182C6A97F4007EB227 /* RelayDetailView.swift in Sources */, + D73E5F192C6A97F4007EB227 /* RelayToggle.swift in Sources */, + D73E5F1A2C6A97F4007EB227 /* RelayStatusView.swift in Sources */, + D73E5F1B2C6A97F4007EB227 /* RelayType.swift in Sources */, + D73E5F1C2C6A97F4007EB227 /* SignalView.swift in Sources */, + D73E5F1D2C6A97F4007EB227 /* RelayPicView.swift in Sources */, + D73E5F1E2C6A97F4007EB227 /* UserSearch.swift in Sources */, + D73E5F202C6A97F4007EB227 /* MuteDurationMenu.swift in Sources */, + D73E5F212C6A97F4007EB227 /* MutelistView.swift in Sources */, + D73E5F222C6A97F4007EB227 /* HighlightView.swift in Sources */, + D73E5F232C6A97F4007EB227 /* HighlightDescription.swift in Sources */, + D73E5F242C6A97F4007EB227 /* HighlightLink.swift in Sources */, + D73E5F252C6A97F4007EB227 /* HighlightEventRef.swift in Sources */, + D73E5F262C6A97F4007EB227 /* HighlightDraftContentView.swift in Sources */, + D73E5F272C6A97F4007EB227 /* TimeDot.swift in Sources */, + D73E5F282C6A97F4007EB227 /* EventTop.swift in Sources */, + D73E5F292C6A97F4007EB227 /* ReplyDescription.swift in Sources */, + D73E5F2A2C6A97F4007EB227 /* RelativeTime.swift in Sources */, + D73E5F732C6A9885007EB227 /* TestData.swift in Sources */, + D73E5F2B2C6A97F4007EB227 /* ReplyPart.swift in Sources */, + D73E5F2C2C6A97F4007EB227 /* ProxyView.swift in Sources */, + D73E5F2D2C6A97F4007EB227 /* SelectedEventView.swift in Sources */, + D73E5F2E2C6A97F4007EB227 /* EventBody.swift in Sources */, + D73E5F302C6A97F4007EB227 /* EventProfile.swift in Sources */, + D73E5F312C6A97F4007EB227 /* EventMenu.swift in Sources */, + D73E5F322C6A97F4007EB227 /* EventMutingContainerView.swift in Sources */, + D73E5F332C6A97F4007EB227 /* ZapEvent.swift in Sources */, + D73E5F342C6A97F4007EB227 /* TextEvent.swift in Sources */, + D73E5F352C6A97F4007EB227 /* WideEventView.swift in Sources */, + D73E5F8A2C6AA69C007EB227 /* SideMenuView.swift in Sources */, + D73E5F362C6A97F4007EB227 /* LongformView.swift in Sources */, + D73E5F372C6A97F4007EB227 /* LongformPreview.swift in Sources */, + D73E5F382C6A97F4007EB227 /* EventShell.swift in Sources */, + D73E5F882C6AA661007EB227 /* NostrScript.swift in Sources */, + D73E5F392C6A97F4007EB227 /* MentionView.swift in Sources */, + D73E5F3A2C6A97F4007EB227 /* EventLoaderView.swift in Sources */, + D73E5F3B2C6A97F4007EB227 /* RepostView.swift in Sources */, + D73E5F3C2C6A97F4007EB227 /* RepostedEvent.swift in Sources */, + D73E5F3D2C6A97F4007EB227 /* QuoteRepostsView.swift in Sources */, + D73E5F3E2C6A97F4007EB227 /* ReactionView.swift in Sources */, + D73E5F3F2C6A97F4007EB227 /* EventActionBar.swift in Sources */, + D73E5F402C6A97F5007EB227 /* EventDetailBar.swift in Sources */, + D73E5F412C6A97F5007EB227 /* ShareAction.swift in Sources */, + D73E5F422C6A97F5007EB227 /* RepostAction.swift in Sources */, + D73E5F942C6AA74D007EB227 /* EULAView.swift in Sources */, + D73E5F432C6A97F5007EB227 /* ShareActionButton.swift in Sources */, + D73E5F442C6A97F5007EB227 /* BigButton.swift in Sources */, + D73E5F8D2C6AA6D7007EB227 /* AddMuteItemView.swift in Sources */, + D73E5F452C6A97F5007EB227 /* AddRelayView.swift in Sources */, + D73E5F462C6A97F5007EB227 /* BlocksView.swift in Sources */, + D73E5F472C6A97F5007EB227 /* BookmarksView.swift in Sources */, + D73E5F482C6A97F5007EB227 /* CarouselView.swift in Sources */, + D73E5F492C6A97F5007EB227 /* ConfigView.swift in Sources */, + D73E5F4A2C6A97F5007EB227 /* CreateAccountView.swift in Sources */, + D73E5F7A2C6A9C55007EB227 /* NotificationFormatter.swift in Sources */, + D73E5F4B2C6A97F5007EB227 /* DirectMessagesView.swift in Sources */, + D73E5F4C2C6A97F5007EB227 /* DMChatView.swift in Sources */, + D73E5F962C6AA7B0007EB227 /* ConnectWalletView.swift in Sources */, + D73E5F4D2C6A97F5007EB227 /* DMView.swift in Sources */, + D73E5F4E2C6A97F5007EB227 /* EmptyTimelineView.swift in Sources */, + D73E5F4F2C6A97F5007EB227 /* EmptyUserSearchView.swift in Sources */, + D73E5F502C6A97F5007EB227 /* EventView.swift in Sources */, + D73E5F512C6A97F5007EB227 /* EventDetailView.swift in Sources */, + D73E5F522C6A97F5007EB227 /* FollowButtonView.swift in Sources */, + D73E5F532C6A97F5007EB227 /* FollowingView.swift in Sources */, + D73E5F542C6A97F5007EB227 /* LoginView.swift in Sources */, + D73E5F552C6A97F5007EB227 /* QRScanNSECView.swift in Sources */, + D73E5F562C6A97F5007EB227 /* NoteContentView.swift in Sources */, + D73E5F572C6A97F5007EB227 /* PostButton.swift in Sources */, + D73E5F582C6A97F5007EB227 /* MediaPicker.swift in Sources */, + D73E5F592C6A97F5007EB227 /* TextViewWrapper.swift in Sources */, + D73E5F5A2C6A97F5007EB227 /* MainTabView.swift in Sources */, + D73E5F5B2C6A97F5007EB227 /* PubkeyView.swift in Sources */, + D73E5F5C2C6A97F5007EB227 /* ReplyView.swift in Sources */, + D73E5F5D2C6A97F5007EB227 /* ParticipantsView.swift in Sources */, + D73E5F5E2C6A97F5007EB227 /* SaveKeysView.swift in Sources */, + D73E5F5F2C6A97F5007EB227 /* SearchHomeView.swift in Sources */, + D73E5F602C6A97F5007EB227 /* SearchResultsView.swift in Sources */, + D73E5F612C6A97F5007EB227 /* SearchView.swift in Sources */, + D73E5F622C6A97F5007EB227 /* SelectWalletView.swift in Sources */, + D73E5F642C6A97F5007EB227 /* ThreadView.swift in Sources */, + D73E5F652C6A97F5007EB227 /* TimelineView.swift in Sources */, + D73E5F662C6A97F5007EB227 /* UserRelaysView.swift in Sources */, + D73E5F682C6A97F5007EB227 /* BannerImageView.swift in Sources */, + D73E5F692C6A97F5007EB227 /* ReactionsView.swift in Sources */, + D73E5F6A2C6A97F5007EB227 /* ReportView.swift in Sources */, + D73E5F6C2C6A97F5007EB227 /* RepostsView.swift in Sources */, + D73E5F6D2C6A97F5007EB227 /* Launch.storyboard in Sources */, + D73E5F6F2C6A97F5007EB227 /* RelayFilterView.swift in Sources */, + D703D78A2C670C8A00A400EA /* LibreTranslateServer.swift in Sources */, + D703D7602C670AAB00A400EA /* MigratedTypes.swift in Sources */, + D73E5F742C6A9890007EB227 /* damusApp.swift in Sources */, + D73E5E192C6A965A007EB227 /* DamusState.swift in Sources */, + D703D74F2C6709ED00A400EA /* nostrdb.c in Sources */, + D73E5F872C6AA639007EB227 /* ImageCarousel.swift in Sources */, + D703D7932C670DAF00A400EA /* mem.c in Sources */, + D703D7732C670B8500A400EA /* Offset.swift in Sources */, + D703D7572C670A5A00A400EA /* IdType.swift in Sources */, + D703D7542C670A2A00A400EA /* MediaUploader.swift in Sources */, + D703D7B72C67118F00A400EA /* StringUtil.swift in Sources */, + D73E5E1A2C6A9665007EB227 /* RelayPool.swift in Sources */, + D703D74C2C6709CE00A400EA /* Zaps.swift in Sources */, + D703D7552C670A3700A400EA /* DamusUserDefaults.swift in Sources */, + D703D7A32C670E1D00A400EA /* nostr_bech32.c in Sources */, + D703D7992C670DF900A400EA /* sha256.c in Sources */, + D703D7972C670DED00A400EA /* wasm.c in Sources */, + D703D7842C670C4700A400EA /* SequenceUtils.swift in Sources */, + D703D7912C670D1E00A400EA /* DisplayName.swift in Sources */, + D703D7B02C6710A500A400EA /* Root.swift in Sources */, + D703D7822C670C3400A400EA /* InsertSort.swift in Sources */, + D703D79E2C670E0F00A400EA /* hex.c in Sources */, + D703D7B12C6710AB00A400EA /* LocalizationUtil.swift in Sources */, + D703D74D2C6709D400A400EA /* Zap.swift in Sources */, + D73E5E1C2C6A9677007EB227 /* DirectMessagesModel.swift in Sources */, + D703D7762C670BCA00A400EA /* Verifier.swift in Sources */, + D703D75A2C670A7900A400EA /* LNUrls.swift in Sources */, + D703D74B2C6709C900A400EA /* NoteId.swift in Sources */, + D703D7B52C67111C00A400EA /* CollectionExtension.swift in Sources */, + D703D7722C670B8000A400EA /* FlatBufferBuilder.swift in Sources */, + D703D7502C6709F500A400EA /* NdbTxn.swift in Sources */, + D703D77E2C670C1100A400EA /* NostrKind.swift in Sources */, + D73E5F972C6AA7B7007EB227 /* SuggestedHashtagsView.swift in Sources */, + D703D7B22C6710AF00A400EA /* ContentParsing.swift in Sources */, + D703D79F2C670E1200A400EA /* amount.c in Sources */, + D703D7522C670A1400A400EA /* Log.swift in Sources */, + D73E5E1B2C6A9672007EB227 /* LikeCounter.swift in Sources */, + D703D7A92C670E5A00A400EA /* refmap.c in Sources */, + D703D77B2C670BF000A400EA /* TableVerifier.swift in Sources */, + D73E5F7B2C6A9D0F007EB227 /* Router.swift in Sources */, + D703D76D2C670B4500A400EA /* ZapDataModel.swift in Sources */, + D703D79D2C670E0700A400EA /* node_id.c in Sources */, + D703D79B2C670E0000A400EA /* bech32_util.c in Sources */, + D703D75D2C670A8E00A400EA /* ReferencedId.swift in Sources */, + D703D7772C670BCE00A400EA /* Verifiable.swift in Sources */, + D703D7642C670AE300A400EA /* StringCodable.swift in Sources */, + D703D7A52C670E3E00A400EA /* mdb.c in Sources */, + D703D76B2C670B3100A400EA /* Referenced.swift in Sources */, + D703D7952C670DE600A400EA /* hash_u5.c in Sources */, + D703D7582C670A6000A400EA /* Id.swift in Sources */, + D703D76E2C670B4900A400EA /* NdbTagsIterator.swift in Sources */, + D703D7A02C670E1500A400EA /* take.c in Sources */, + D703D7692C670B2600A400EA /* Block.swift in Sources */, + D703D77D2C670C0300A400EA /* FlatbuffersErrors.swift in Sources */, + D703D7A62C670E5200A400EA /* builder.c in Sources */, + D703D78D2C670CAF00A400EA /* UpdateStatsNotify.swift in Sources */, + D703D75C2C670A8400A400EA /* NdbNote.swift in Sources */, + D703D7592C670A7300A400EA /* Profiles.swift in Sources */, + D703D7512C6709FB00A400EA /* Nostr.swift in Sources */, + D703D7652C670AF500A400EA /* NdbTagIterator.swift in Sources */, + D703D77F2C670C1600A400EA /* ThreadReply.swift in Sources */, + D703D7742C670B8A00A400EA /* FbConstants.swift in Sources */, + D703D7B82C6711A000A400EA /* NativeObject.swift in Sources */, + D703D7462C67091A00A400EA /* Keys.swift in Sources */, + D703D7882C670C8200A400EA /* FriendFilter.swift in Sources */, + D703D7562C670A4C00A400EA /* TranslationService.swift in Sources */, + D703D7A72C670E5500A400EA /* json_parser.c in Sources */, + D703D79C2C670E0300A400EA /* tal.c in Sources */, + D703D7712C670B6D00A400EA /* NdbProfile.swift in Sources */, + D703D7A22C670E1A00A400EA /* list.c in Sources */, + D703D7A42C670E3C00A400EA /* midl.c in Sources */, + D703D7982C670DF200A400EA /* utf8.c in Sources */, + D703D78B2C670C9500A400EA /* MakeZapRequest.swift in Sources */, + D703D7862C670C6500A400EA /* NewUnmutesNotify.swift in Sources */, + D703D7662C670AFC00A400EA /* AsciiCharacter.swift in Sources */, + D703D7682C670B1400A400EA /* Mentions.swift in Sources */, + D703D7432C67084F00A400EA /* Ndb.swift in Sources */, + D703D7B32C6710BF00A400EA /* NewMutesNotify.swift in Sources */, + D703D78C2C670CAB00A400EA /* ProofOfWork.swift in Sources */, + D703D7A12C670E1700A400EA /* talstr.c in Sources */, + D703D7782C670BD900A400EA /* LNUrlPayRequest.swift in Sources */, + D703D7612C670AC000A400EA /* FlatBufferObject.swift in Sources */, + D703D7942C670DE300A400EA /* bolt11.c in Sources */, + D703D74A2C6709C200A400EA /* MuteItem.swift in Sources */, + D703D77C2C670BFB00A400EA /* Enum.swift in Sources */, + D73E5E1F2C6A969E007EB227 /* RelayModelCache.swift in Sources */, + D703D7AB2C670F6900A400EA /* UnmuteThreadNotify.swift in Sources */, + D703D7702C670B5F00A400EA /* UserStatus.swift in Sources */, + D703D7752C670BBF00A400EA /* Constants.swift in Sources */, + D703D7832C670C3900A400EA /* damus.c in Sources */, + D73E5E172C6A962A007EB227 /* ImageUploadModel.swift in Sources */, + D703D76A2C670B2C00A400EA /* Bech32Object.swift in Sources */, + D73E5E162C6A9619007EB227 /* PostView.swift in Sources */, + D703D7872C670C7E00A400EA /* DamusPurpleEnvironment.swift in Sources */, + D703D7892C670C8600A400EA /* DeepLPlan.swift in Sources */, + D73E5E182C6A963D007EB227 /* AttachMediaUtility.swift in Sources */, + D73E5F852C6AA628007EB227 /* LoadScript.swift in Sources */, + D703D74E2C6709DA00A400EA /* Pubkey.swift in Sources */, + D703D7802C670C2500A400EA /* NIP05.swift in Sources */, + D703D7AA2C670E5D00A400EA /* verifier.c in Sources */, + D73E5E1D2C6A9680007EB227 /* PreviewCache.swift in Sources */, + D703D78E2C670CEF00A400EA /* Table.swift in Sources */, + D73E5F892C6AA670007EB227 /* BuilderEventView.swift in Sources */, + D703D7452C67090200A400EA /* MutelistManager.swift in Sources */, + D703D7B42C6710F200A400EA /* Int+extension.swift in Sources */, + D703D7A82C670E5800A400EA /* emitter.c in Sources */, + D703D76F2C670B5200A400EA /* NostrResponse.swift in Sources */, + D703D7902C670D1600A400EA /* NewEventsBits.swift in Sources */, + D703D7962C670DEA00A400EA /* error.c in Sources */, + D703D75E2C670A9A00A400EA /* NdbTagElem.swift in Sources */, + D703D7622C670ACB00A400EA /* ByteBuffer.swift in Sources */, + D703D79A2C670DFD00A400EA /* bech32.c in Sources */, + D703D7B62C67118200A400EA /* String+extension.swift in Sources */, + D703D76C2C670B3900A400EA /* Post.swift in Sources */, + D703D77A2C670BEB00A400EA /* VeriferOptions.swift in Sources */, + D73E5F9E2C6AA9F7007EB227 /* nostrscript.c in Sources */, + D703D71E2C66E47100A400EA /* ActionViewController.swift in Sources */, + D703D7472C67092700A400EA /* UserSettingsStore.swift in Sources */, + D703D7852C670C6100A400EA /* Notify.swift in Sources */, + D703D7532C670A2600A400EA /* Wallet.swift in Sources */, + D703D75F2C670AA200A400EA /* NostrEvent.swift in Sources */, + D703D7442C67086800A400EA /* HeadlessDamusState.swift in Sources */, + D703D7922C670D2900A400EA /* RelayURL.swift in Sources */, + D703D7632C670ADD00A400EA /* FollowState.swift in Sources */, + D703D7792C670BE100A400EA /* KeychainStorage.swift in Sources */, + D703D78F2C670D0300A400EA /* WalletConnect.swift in Sources */, + D703D7672C670B0F00A400EA /* ZapType.swift in Sources */, + D703D75B2C670A7F00A400EA /* Contacts.swift in Sources */, + D703D7812C670C2B00A400EA /* Bech32.swift in Sources */, + D73E5E1E2C6A9694007EB227 /* RelayFilters.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D79C4C102AFEB061003A41B4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3810,6 +4851,15 @@ target = 4CE6DEE227F7A08100C66700 /* damus */; targetProxy = 4CE6DEFE27F7A08200C66700 /* PBXContainerItemProxy */; }; + D703D7242C66E47100A400EA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D703D7162C66E47100A400EA /* HighlighterActionExtension */; + targetProxy = D703D7232C66E47100A400EA /* PBXContainerItemProxy */; + }; + D703D7AD2C670FA700A400EA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = D703D7AC2C670FA700A400EA /* MarkdownUI */; + }; D79C4C1A2AFEB061003A41B4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D79C4C132AFEB061003A41B4 /* DamusNotificationService */; @@ -4243,6 +5293,75 @@ }; name = Release; }; + D703D7282C66E47100A400EA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = "highlighter action extension/highlighter action extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XK7H4JAB3D; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "highlighter action extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Highlight on Damus"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.jb55.damus2.highlighter-action-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D703D7292C66E47100A400EA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = "highlighter action extension/highlighter action extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XK7H4JAB3D; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "highlighter action extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Highlight on Damus"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.jb55.damus2.highlighter-action-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; D79C4C1E2AFEB061003A41B4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4347,6 +5466,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D703D7272C66E47100A400EA /* Build configuration list for PBXNativeTarget "HighlighterActionExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D703D7282C66E47100A400EA /* Debug */, + D703D7292C66E47100A400EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D79C4C202AFEB061003A41B4 /* Build configuration list for PBXNativeTarget "DamusNotificationService" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -4438,6 +5566,36 @@ package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */; productName = secp256k1; }; + D703D7482C6709B100A400EA /* secp256k1 */ = { + isa = XCSwiftPackageProductDependency; + package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */; + productName = secp256k1; + }; + D703D7AC2C670FA700A400EA /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + package = 4C27C9302A64766F007DBC75 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; + productName = MarkdownUI; + }; + D703D7AE2C670FB700A400EA /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + package = 4C27C9302A64766F007DBC75 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; + productName = MarkdownUI; + }; + D73E5F752C6A997E007EB227 /* EmojiPicker */ = { + isa = XCSwiftPackageProductDependency; + package = 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */; + productName = EmojiPicker; + }; + D73E5F9A2C6AA8B0007EB227 /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; + D73E5F9C2C6AA8E3007EB227 /* SwipeActions */ = { + isa = XCSwiftPackageProductDependency; + package = D78DB8572C1CE9CA00F0AB12 /* XCRemoteSwiftPackageReference "SwipeActions" */; + productName = SwipeActions; + }; D789D11F2AFEFBF20083A7AB /* secp256k1 */ = { isa = XCSwiftPackageProductDependency; package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */; diff --git a/damus.xcodeproj/xcshareddata/xcschemes/HighlighterActionExtension.xcscheme b/damus.xcodeproj/xcshareddata/xcschemes/HighlighterActionExtension.xcscheme new file mode 100644 index 00000000..7f1479ae --- /dev/null +++ b/damus.xcodeproj/xcshareddata/xcschemes/HighlighterActionExtension.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/damus/Components/InvoiceView.swift b/damus/Components/InvoiceView.swift index cc99f741..36172a67 100644 --- a/damus/Components/InvoiceView.swift +++ b/damus/Components/InvoiceView.swift @@ -94,8 +94,8 @@ enum OpenWalletError: Error { } func open_with_wallet(wallet: Wallet.Model, invoice: String) throws { - if let url = URL(string: "\(wallet.link)\(invoice)"), UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url) + if let url = URL(string: "\(wallet.link)\(invoice)"), this_app.canOpenURL(url) { + this_app.open(url) } else { guard let store_link = wallet.appStoreLink else { throw OpenWalletError.no_wallet_to_open @@ -105,11 +105,11 @@ func open_with_wallet(wallet: Wallet.Model, invoice: String) throws { throw OpenWalletError.store_link_invalid } - guard UIApplication.shared.canOpenURL(url) else { + guard this_app.canOpenURL(url) else { throw OpenWalletError.system_cannot_open_store_link } - UIApplication.shared.open(url) + this_app.open(url) } } @@ -122,8 +122,3 @@ struct InvoiceView_Previews: PreviewProvider { .frame(width: 300, height: 200) } } - - -func present_sheet(_ sheet: Sheets) { - notify(.present_sheet(sheet)) -} diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 3b1172dd..886d74ef 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -57,6 +57,10 @@ enum Sheets: Identifiable { } } +func present_sheet(_ sheet: Sheets) { + notify(.present_sheet(sheet)) +} + struct ContentView: View { let keypair: Keypair let appDelegate: AppDelegate? @@ -877,7 +881,7 @@ func update_filters_with_since(last_of_kind: [UInt32: NostrEvent], filters: [Nos func setup_notifications() { - UIApplication.shared.registerForRemoteNotifications() + this_app.registerForRemoteNotifications() let center = UNUserNotificationCenter.current() center.getNotificationSettings { settings in diff --git a/damus/Models/Camera/CameraService.swift b/damus/Models/Camera/CameraService.swift index 9eee7ee5..f48b4da9 100644 --- a/damus/Models/Camera/CameraService.swift +++ b/damus/Models/Camera/CameraService.swift @@ -151,7 +151,7 @@ public class CameraService: NSObject, Identifiable { DispatchQueue.main.async { self.alertError = AlertError(title: "Camera Access", message: "Damus needs camera and microphone access. Enable in settings.", primaryButtonTitle: "Go to settings", secondaryButtonTitle: nil, primaryAction: { - UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, + this_app.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) }, secondaryAction: nil) diff --git a/damus/Util/DamusAliases.swift b/damus/Util/DamusAliases.swift new file mode 100644 index 00000000..b3288468 --- /dev/null +++ b/damus/Util/DamusAliases.swift @@ -0,0 +1,11 @@ +// +// DamusAliases.swift +// damus +// +// Created by Daniel D’Aquino on 2024-08-12. +// + +import Foundation +import UIKit + +let this_app: UIApplication = UIApplication.shared diff --git a/damus/Util/InputDismissKeyboard.swift b/damus/Util/InputDismissKeyboard.swift index ab39b1b7..a0a42574 100644 --- a/damus/Util/InputDismissKeyboard.swift +++ b/damus/Util/InputDismissKeyboard.swift @@ -30,7 +30,7 @@ public struct DismissKeyboardOnTap: ViewModifier { } public func end_editing() { - UIApplication.shared.connectedScenes + this_app.connectedScenes .filter {$0.activationState == .foregroundActive} .map {$0 as? UIWindowScene} .compactMap({$0}) diff --git a/damus/Util/Theme.swift b/damus/Util/Theme.swift index e776b06f..d7de3f3f 100644 --- a/damus/Util/Theme.swift +++ b/damus/Util/Theme.swift @@ -11,8 +11,7 @@ import UIKit class Theme { static var safeAreaInsets: UIEdgeInsets? { - return UIApplication - .shared + return this_app .connectedScenes .flatMap { ($0 as? UIWindowScene)?.windows ?? [] } .first { $0.isKeyWindow }?.safeAreaInsets diff --git a/damus/Views/AddRelayView.swift b/damus/Views/AddRelayView.swift index 9e7413b8..7b12a428 100644 --- a/damus/Views/AddRelayView.swift +++ b/damus/Views/AddRelayView.swift @@ -116,7 +116,7 @@ struct AddRelayView: View { } new_relay = "" - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + this_app.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) dismiss() }) { diff --git a/damus/Views/Muting/AddMuteItemView.swift b/damus/Views/Muting/AddMuteItemView.swift index cbc4f974..73d74260 100644 --- a/damus/Views/Muting/AddMuteItemView.swift +++ b/damus/Views/Muting/AddMuteItemView.swift @@ -87,7 +87,7 @@ struct AddMuteItemView: View { new_text = "" - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + this_app.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) dismiss() }) { diff --git a/damus/Views/Notifications/DamusAppNotificationView.swift b/damus/Views/Notifications/DamusAppNotificationView.swift index 27f5e41b..a866ca22 100644 --- a/damus/Views/Notifications/DamusAppNotificationView.swift +++ b/damus/Views/Notifications/DamusAppNotificationView.swift @@ -96,7 +96,7 @@ struct DamusAppNotificationView: View { @MainActor func open_url(url: URL) { - UIApplication.shared.open(url) + this_app.open(url) } var body: some View { diff --git a/damus/Views/Zaps/CustomizeZapView.swift b/damus/Views/Zaps/CustomizeZapView.swift index 5b6b8ebe..703152c8 100644 --- a/damus/Views/Zaps/CustomizeZapView.swift +++ b/damus/Views/Zaps/CustomizeZapView.swift @@ -356,7 +356,7 @@ struct ZapSheetViewIfPossible: View { extension View { func hideKeyboard() { let resign = #selector(UIResponder.resignFirstResponder) - UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil) + this_app.sendAction(resign, to: nil, from: nil, for: nil) } } diff --git a/highlighter action extension/ActionViewController.swift b/highlighter action extension/ActionViewController.swift new file mode 100644 index 00000000..a1ef5c46 --- /dev/null +++ b/highlighter action extension/ActionViewController.swift @@ -0,0 +1,250 @@ +// +// ActionViewController.swift +// highlighter action extension +// +// Created by Daniel D’Aquino on 2024-08-09. +// + +import UIKit +import MobileCoreServices +import UniformTypeIdentifiers +import SwiftUI + +struct ShareExtensionView: View { + @State var highlighter_state: HighlighterState = .loading + let extensionContext: NSExtensionContext + @State var state: DamusState? = nil + @State var signedEvent: String? = nil + + @State private var selectedText = "" + @State private var selectedTextHeight: CGFloat = .zero + @State private var selectedTextWidth: CGFloat = .zero + + var body: some View { + VStack(spacing: 15) { + if let state { + switch self.highlighter_state { + case .loading: + ProgressView() + case .no_highlight_text: + Group { + Text("No text selected", comment: "Title indicating that a highlight cannot be posted because no text was selected.") + .font(.largeTitle) + .multilineTextAlignment(.center) + .padding() + Text("You cannot post a highlight because you have selected no text on the page! Please close this, select some text, and try again.", comment: "Label explaining a highlight cannot be made because there was no selected text, and some instructions on how to resolve the issue") + .multilineTextAlignment(.center) + Button(action: { + self.done() + }, label: { + Text("Close", comment: "Button label giving the user the option to close the sheet from which they were trying to post a highlight") + }) + .foregroundStyle(.secondary) + } + case .not_logged_in: + Group { + Text("Not logged in", comment: "Title indicating that a highlight cannot be posted because the user is not logged in.") + .font(.largeTitle) + .multilineTextAlignment(.center) + .padding() + Text("You cannot post a highlight because you are not logged in with a private key! Please close this, login with a private key (or nsec), and try again.", comment: "Label explaining a highlight cannot be made because the user is not logged in") + .multilineTextAlignment(.center) + Button(action: { + self.done() + }, label: { + Text("Close", comment: "Button label giving the user the option to close the sheet from which they were trying to post a highlight") + }) + .foregroundStyle(.secondary) + } + case .loaded(let highlighted_text, let source_url): + PostView( + action: .highlighting(HighlightContentDraft(selected_text: highlighted_text, source: .external_url(source_url))), + damus_state: state + ) + case .failed(let error): + Group { + Text("Error", comment: "Title indicating that an error has occurred.") + .font(.largeTitle) + .multilineTextAlignment(.center) + .padding() + Text("An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below.", comment: "Label explaining there was an error, and suggesting next steps") + .multilineTextAlignment(.center) + Text("Error: \(error)") + Button(action: { + self.done() + }, label: { + Text("Close", comment: "Button label giving the user the option to close the sheet from which they were trying to post a highlight") + }) + .foregroundStyle(.secondary) + } + case .posted(event: let event): + Group { + Image(systemName: "checkmark.circle.fill") + .resizable() + .frame(width: 60, height: 60) + Text("Posted", comment: "Title indicating that the user has posted a highlight successfully") + .font(.largeTitle) + .multilineTextAlignment(.center) + .padding(.bottom) + + Link(destination: URL(string: "damus:\(event.id.bech32)")!, label: { + Text("Go to the app", comment: "Button label giving the user the option to go to the app after posting a highlight") + }) + .buttonStyle(GradientButtonStyle()) + Button(action: { + self.done() + }, label: { + Text("Close", comment: "Button label giving the user the option to close the sheet from which they posted a highlight") + }) + .foregroundStyle(.secondary) + } + case .cancelled: + Group { + Text("Cancelled", comment: "Title indicating that the user has cancelled.") + .font(.largeTitle) + .padding() + Button(action: { + self.done() + }, label: { + Text("Close", comment: "Button label giving the user the option to close the sheet from which they were trying to post a highlight") + }) + .foregroundStyle(.secondary) + } + case .posting: + Group { + ProgressView() + .frame(width: 20, height: 20) + Text("Posting", comment: "Title indicating that the highlight post is being published to the network") + .font(.largeTitle) + .multilineTextAlignment(.center) + .padding(.bottom) + Text("Your highlight is being broadcasted to the network. Please wait.", comment: "Label explaining there their highlight publishing action is in progress") + .multilineTextAlignment(.center) + .padding() + } + } + } + } + .onAppear(perform: { + self.loadSharedUrl() + guard let keypair = get_saved_keypair() else { return } + guard keypair.privkey != nil else { + self.highlighter_state = .not_logged_in + return + } + self.state = DamusState(keypair: keypair) + }) + .onChange(of: self.highlighter_state) { + if case .cancelled = highlighter_state { + self.done() + } + } + .onReceive(handle_notify(.post)) { post_notification in + switch post_notification { + case .post(let post): + self.post(post) + case .cancel: + self.highlighter_state = .cancelled + } + } + } + + func loadSharedUrl() { + guard + let extensionItem = extensionContext.inputItems.first as? NSExtensionItem, + let itemProvider = extensionItem.attachments?.first else { + self.highlighter_state = .failed(error: "Can't get itemProvider") + return + } + + let propertyList = UTType.propertyList.identifier + if itemProvider.hasItemConformingToTypeIdentifier(propertyList) { + itemProvider.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in + guard let dictionary = item as? NSDictionary else { return } + if error != nil { + self.highlighter_state = .failed(error: "Error loading plist item: \(error?.localizedDescription ?? "Unknown")") + return + } + OperationQueue.main.addOperation { + if let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary, + let urlString = results["URL"] as? String, + let selection = results["selectedText"] as? String, + let url = URL(string: urlString) { + guard selection != "" else { + self.highlighter_state = .no_highlight_text + return + } + self.highlighter_state = .loaded(highlighted_text: selection, source_url: url) + } + else { + self.highlighter_state = .failed(error: "Cannot load results") + } + } + }) + } + else { + self.highlighter_state = .failed(error: "No plist detected") + } + } + + func post(_ post: NostrPost) { + self.highlighter_state = .posting + guard let state else { + self.highlighter_state = .failed(error: "Damus state not initialized") + return + } + guard let full_keypair = state.keypair.to_full() else { + self.highlighter_state = .not_logged_in + return + } + guard let posted_event = post.to_event(keypair: full_keypair) else { + self.highlighter_state = .failed(error: "Cannot convert post data into a nostr event") + return + } + state.postbox.send(posted_event, on_flush: .once({ flushed_event in + if flushed_event.event.id == posted_event.id { + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { // Offset labor perception bias + self.highlighter_state = .posted(event: flushed_event.event) + }) + } + else { + self.highlighter_state = .failed(error: "Flushed event is not the event we just tried to post.") + } + })) + } + + func done() { + self.extensionContext.completeRequest(returningItems: [], completionHandler: nil) + } + + enum HighlighterState: Equatable { + case loading + case no_highlight_text + case not_logged_in + case loaded(highlighted_text: String, source_url: URL) + case posting + case posted(event: NostrEvent) + case cancelled + case failed(error: String) + } +} + +class ActionViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + self.view.tintColor = UIColor(DamusColors.purple) + + DispatchQueue.main.async { + let contentView = UIHostingController(rootView: ShareExtensionView(extensionContext: self.extensionContext!)) + self.addChild(contentView) + self.view.addSubview(contentView.view) + + // set up constraints + contentView.view.translatesAutoresizingMaskIntoConstraints = false + contentView.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true + contentView.view.bottomAnchor.constraint (equalTo: self.view.bottomAnchor).isActive = true + contentView.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true + contentView.view.rightAnchor.constraint (equalTo: self.view.rightAnchor).isActive = true + } + } +} diff --git a/highlighter action extension/HighlighterExtensionAliases.swift b/highlighter action extension/HighlighterExtensionAliases.swift new file mode 100644 index 00000000..1256466b --- /dev/null +++ b/highlighter action extension/HighlighterExtensionAliases.swift @@ -0,0 +1,11 @@ +// +// HighlighterExtensionAliases.swift +// highlighter action extension +// +// Created by Daniel D’Aquino on 2024-08-12. +// + +import Foundation +import UIKit + +let this_app: UIApplication = UIApplication() diff --git a/highlighter action extension/Info.plist b/highlighter action extension/Info.plist new file mode 100644 index 00000000..00f8a208 --- /dev/null +++ b/highlighter action extension/Info.plist @@ -0,0 +1,30 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + TRUEPREDICATE + NSExtensionJavaScriptPreprocessingFile + getSelection + NSExtensionServiceAllowsFinderPreviewItem + + NSExtensionServiceAllowsTouchBarItem + + NSExtensionServiceFinderPreviewIconName + NSActionTemplate + NSExtensionServiceTouchBarBezelColorName + TouchBarBezel + NSExtensionServiceTouchBarIconName + NSActionTemplate + + NSExtensionPointIdentifier + com.apple.ui-services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ActionViewController + + + diff --git a/highlighter action extension/Media.xcassets/AppIconExtension.appiconset/Contents.json b/highlighter action extension/Media.xcassets/AppIconExtension.appiconset/Contents.json new file mode 100644 index 00000000..9a539cd2 --- /dev/null +++ b/highlighter action extension/Media.xcassets/AppIconExtension.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "highlighter.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/highlighter action extension/Media.xcassets/AppIconExtension.appiconset/highlighter.png b/highlighter action extension/Media.xcassets/AppIconExtension.appiconset/highlighter.png new file mode 100644 index 0000000000000000000000000000000000000000..dd36ab1a42bd17a16d70d831d036fd9754c3c439 GIT binary patch literal 52889 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 + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.com.damus + + com.apple.security.network.client + + keychain-access-groups + + $(AppIdentifierPrefix)com.jb55.damus2 + + + From b43dcd2bc710a8ebf2f7688981190317d9fc72c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Thu, 22 Aug 2024 14:34:10 -0700 Subject: [PATCH 06/10] Fix highlight tag ambiguity with specifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes the ambiguity in tags used in highlights with comments, by adding specifiers to help clients understand: - If a URL reference is the source of the highlight or just a URL mentioned in the comment - If a pubkey reference is the author of the highlighted content, or just a generic mention in the comment This tries to be backwards compatible with previous versions of NIP-84. Testing -------- PASS Device: iPhone 15 simulator iOS: 17.5 Damus: This commit Steps: 1. Create a new highlight from a webpage using the extension. Tag a user and attach an image 2. Check the newly-created highlight: 1. Highlight description line should just say "Highlighted", not "Highlighted " 2. Highlight source link preview should present the URL of the highlighted page, NOT the image URL 3. Inspect the JSON for the newly-created highlight: 1. "r" tags should include specifiers in the 3rd slot, such as "source" or "mention" 2. "p" tags should include specifiers in the 3rd slot, such as "mention" 4. Go to an older, generic highlight (without comment) to another nostr event and check the view. 1. Highlight description line should say "Highlighted " 2. Clicking on the highlight should lead to the highlighted event itself. Signed-off-by: Daniel D’Aquino --- damus/Models/HighlightEvent.swift | 159 +++++++++++++++++- damus/Models/Mentions.swift | 34 ---- damus/Models/Post.swift | 46 ++++- damus/Views/Events/Components/ReplyPart.swift | 3 +- .../Highlight/HighlightDescription.swift | 30 +--- damus/Views/PostView.swift | 12 +- 6 files changed, 212 insertions(+), 72 deletions(-) diff --git a/damus/Models/HighlightEvent.swift b/damus/Models/HighlightEvent.swift index 024c7273..a269c665 100644 --- a/damus/Models/HighlightEvent.swift +++ b/damus/Models/HighlightEvent.swift @@ -13,24 +13,176 @@ struct HighlightEvent { var event_ref: String? = nil var url_ref: URL? = nil var context: String? = nil + + // MARK: - Initializers and parsers static func parse(from ev: NostrEvent) -> HighlightEvent { var highlight = HighlightEvent(event: ev) + + var best_url_source: (url: URL, tagged_as_source: Bool)? = nil for tag in ev.tags { guard tag.count >= 2 else { continue } switch tag[0].string() { case "e": highlight.event_ref = tag[1].string() case "a": highlight.event_ref = tag[1].string() - case "r": highlight.url_ref = URL(string: tag[1].string()) + case "r": + if tag.count >= 3, + tag[2].string() == HighlightSource.TAG_SOURCE_ELEMENT, + let url = URL(string: tag[1].string()) { + // URL marked as source. Very good candidate + best_url_source = (url: url, tagged_as_source: true) + } + else if tag.count >= 3 && tag[2].string() != HighlightSource.TAG_SOURCE_ELEMENT { + // URL marked as something else (not source). Not the source we are after + } + else if let url = URL(string: tag[1].string()), tag.count == 2 { + // Unmarked URL. This might be what we are after (For NIP-84 backwards compatibility) + if (best_url_source?.tagged_as_source ?? false) == false { + // No URL candidates marked as the source. Mark this as the best option we have + best_url_source = (url: url, tagged_as_source: false) + } + } case "context": highlight.context = tag[1].string() default: break } } + + if let best_url_source { + highlight.url_ref = best_url_source.url + } return highlight } + + // MARK: - Getting information about source + + func source_description_info(highlighted_event: NostrEvent?) -> ReplyDesc { + var others_count = 0 + var highlighted_authors: [Pubkey] = [] + var i = event.tags.count + + if let highlighted_event { + highlighted_authors.append(highlighted_event.pubkey) + } + + for tag in event.tags { + if let pubkey_with_role = PubkeyWithRole.from_tag(tag: tag) { + others_count += 1 + if highlighted_authors.count < 2 { + if let highlighted_event, pubkey_with_role.pubkey == highlighted_event.pubkey { + continue + } else { + switch pubkey_with_role.role { + case .author: + highlighted_authors.append(pubkey_with_role.pubkey) + default: + break + } + + } + } + } + i -= 1 + } + + return ReplyDesc(pubkeys: highlighted_authors, others: others_count) + } + + func source_description_text(ndb: Ndb, highlighted_event: NostrEvent?, locale: Locale = Locale.current) -> String { + let description_info = self.source_description_info(highlighted_event: highlighted_event) + let pubkeys = description_info.pubkeys + + let bundle = bundleForLocale(locale: locale) + + if pubkeys.count == 0 { + return NSLocalizedString("Highlighted", bundle: bundle, comment: "Label to indicate that the user is highlighting their own post.") + } + + guard let profile_txn = NdbTxn(ndb: ndb) else { + return "" + } + + let names: [String] = pubkeys.map { pk in + let prof = ndb.lookup_profile_with_txn(pk, txn: profile_txn) + + return Profile.displayName(profile: prof?.profile, pubkey: pk).username.truncate(maxLength: 50) + } + + let uniqueNames: [String] = Array(Set(names)) + return String(format: NSLocalizedString("Highlighted %@", bundle: bundle, comment: "Label to indicate that the user is highlighting 1 user."), locale: locale, uniqueNames.first ?? "") + } +} + +// MARK: - Helper structures + +extension HighlightEvent { + struct PubkeyWithRole: TagKey, TagConvertible { + let pubkey: Pubkey + let role: Role + + var tag: [String] { + if let role_text = self.role.rawValue { + return [keychar.description, self.pubkey.hex(), role_text] + } + else { + return [keychar.description, self.pubkey.hex()] + } + } + + var keychar: AsciiCharacter { "p" } + + static func from_tag(tag: TagSequence) -> HighlightEvent.PubkeyWithRole? { + var i = tag.makeIterator() + + guard tag.count >= 2, + let t0 = i.next(), + let key = t0.single_char, + key == "p", + let t1 = i.next(), + let pubkey = t1.id().map(Pubkey.init) + else { return nil } + + let t3: String? = i.next()?.string() + let role = Role(rawValue: t3) + return PubkeyWithRole(pubkey: pubkey, role: role) + } + + enum Role: RawRepresentable { + case author + case editor + case mention + case other(String) + case no_role + + typealias RawValue = String? + var rawValue: String? { + switch self { + case .author: "author" + case .editor: "editor" + case .mention: "mention" + case .other(let role): role + case .no_role: nil + } + } + + init(rawValue: String?) { + switch rawValue { + case "author": self = .author + case "editor": self = .editor + case "mention": self = .mention + default: + if let rawValue { + self = .other(rawValue) + } + else { + self = .no_role + } + } + } + } + } } struct HighlightContentDraft: Hashable { @@ -39,15 +191,16 @@ struct HighlightContentDraft: Hashable { } enum HighlightSource: Hashable { + static let TAG_SOURCE_ELEMENT = "source" case event(NostrEvent) case external_url(URL) func tags() -> [[String]] { switch self { case .event(let event): - return [ ["e", "\(event.id)"] ] + return [ ["e", "\(event.id)", HighlightSource.TAG_SOURCE_ELEMENT] ] case .external_url(let url): - return [ ["r", "\(url)"] ] + return [ ["r", "\(url)", HighlightSource.TAG_SOURCE_ELEMENT] ] } } diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift index 2f0785f1..7cfcf722 100644 --- a/damus/Models/Mentions.swift +++ b/damus/Models/Mentions.swift @@ -256,37 +256,3 @@ func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? { return nil } - -struct PostTags { - let blocks: [Block] - let tags: [[String]] -} - -/// Convert -func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags { - var new_tags = tags - - for post_block in post_blocks { - switch post_block { - case .mention(let mention): - switch(mention.ref) { - case .note, .nevent: - continue - default: - break - } - - new_tags.append(mention.ref.tag) - case .hashtag(let hashtag): - new_tags.append(["t", hashtag.lowercased()]) - case .text: break - case .invoice: break - case .relay: break - case .url(let url): - new_tags.append(["r", url.absoluteString]) - break - } - } - - return PostTags(blocks: post_blocks, tags: new_tags) -} diff --git a/damus/Models/Post.swift b/damus/Models/Post.swift index 6a222e93..acb6ac6c 100644 --- a/damus/Models/Post.swift +++ b/damus/Models/Post.swift @@ -20,7 +20,7 @@ struct NostrPost { func to_event(keypair: FullKeypair) -> NostrEvent? { let post_blocks = self.parse_blocks() - let post_tags = make_post_tags(post_blocks: post_blocks, tags: self.tags) + let post_tags = self.make_post_tags(post_blocks: post_blocks, tags: self.tags) let content = post_tags.blocks .map(\.asString) .joined(separator: "") @@ -49,6 +49,50 @@ struct NostrPost { return self.content } } + + /// Parse the post's contents to find more tags to apply to the final nostr event + private func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags { + var new_tags = tags + + for post_block in post_blocks { + switch post_block { + case .mention(let mention): + switch(mention.ref) { + case .note, .nevent: + continue + default: + break + } + + if self.kind == .highlight, case .pubkey(_) = mention.ref { + var new_tag = mention.ref.tag + new_tag.append("mention") + new_tags.append(new_tag) + } + else { + new_tags.append(mention.ref.tag) + } + case .hashtag(let hashtag): + new_tags.append(["t", hashtag.lowercased()]) + case .text: break + case .invoice: break + case .relay: break + case .url(let url): + new_tags.append(self.kind == .highlight ? ["r", url.absoluteString, "mention"] : ["r", url.absoluteString]) + break + } + } + + return PostTags(blocks: post_blocks, tags: new_tags) + } +} + +// MARK: - Helper structures and functions + +/// A struct used for temporarily holding tag information that was parsed from a post contents to aid in building a nostr event +fileprivate struct PostTags { + let blocks: [Block] + let tags: [[String]] } func parse_post_blocks(content: String) -> [Block] { diff --git a/damus/Views/Events/Components/ReplyPart.swift b/damus/Views/Events/Components/ReplyPart.swift index 85858299..e6e00807 100644 --- a/damus/Views/Events/Components/ReplyPart.swift +++ b/damus/Views/Events/Components/ReplyPart.swift @@ -17,7 +17,8 @@ struct ReplyPart: View { Group { if event.known_kind == .highlight { let highlighted_note = event.highlighted_note_id().flatMap { events.lookup($0) } - HighlightDescription(event: event, highlighted_event: highlighted_note, ndb: ndb) + let highlight_note = HighlightEvent.parse(from: event) + HighlightDescription(highlight_event: highlight_note, highlighted_event: highlighted_note, ndb: ndb) } else if let reply_ref = event.thread_reply()?.reply { let replying_to = events.lookup(reply_ref.note_id) ReplyDescription(event: event, replying_to: replying_to, ndb: ndb) diff --git a/damus/Views/Events/Highlight/HighlightDescription.swift b/damus/Views/Events/Highlight/HighlightDescription.swift index 5a6d1e19..65d48a8e 100644 --- a/damus/Views/Events/Highlight/HighlightDescription.swift +++ b/damus/Views/Events/Highlight/HighlightDescription.swift @@ -9,12 +9,12 @@ import SwiftUI // Modified from Reply Description struct HighlightDescription: View { - let event: NostrEvent + let highlight_event: HighlightEvent let highlighted_event: NostrEvent? let ndb: Ndb var body: some View { - (Text(Image(systemName: "highlighter")) + Text(verbatim: " \(highlight_desc(ndb: ndb, event: event, highlighted_event: highlighted_event))")) + (Text(Image(systemName: "highlighter")) + Text(verbatim: " \(highlight_event.source_description_text(ndb: ndb, highlighted_event: highlighted_event))")) .font(.footnote) .foregroundColor(.gray) .frame(maxWidth: .infinity, alignment: .leading) @@ -24,30 +24,6 @@ struct HighlightDescription: View { struct HighlightDescription_Previews: PreviewProvider { static var previews: some View { - HighlightDescription(event: test_note, highlighted_event: test_note, ndb: test_damus_state.ndb) + HighlightDescription(highlight_event: HighlightEvent.parse(from: test_note), highlighted_event: nil, ndb: test_damus_state.ndb) } } - -func highlight_desc(ndb: Ndb, event: NostrEvent, highlighted_event: NostrEvent?, locale: Locale = Locale.current) -> String { - let desc = make_reply_description(event, replying_to: highlighted_event) - let pubkeys = desc.pubkeys - - let bundle = bundleForLocale(locale: locale) - - if pubkeys.count == 0 { - return NSLocalizedString("Highlighted", bundle: bundle, comment: "Label to indicate that the user is highlighting their own post.") - } - - guard let profile_txn = NdbTxn(ndb: ndb) else { - return "" - } - - let names: [String] = pubkeys.map { pk in - let prof = ndb.lookup_profile_with_txn(pk, txn: profile_txn) - - return Profile.displayName(profile: prof?.profile, pubkey: pk).username.truncate(maxLength: 50) - } - - let uniqueNames: [String] = Array(Set(names)) - return String(format: NSLocalizedString("Highlighted %@", bundle: bundle, comment: "Label to indicate that the user is highlighting 1 user."), locale: locale, uniqueNames.first ?? "") -} diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift index 23b8a4ea..2aab4298 100644 --- a/damus/Views/PostView.swift +++ b/damus/Views/PostView.swift @@ -702,11 +702,6 @@ func build_post(state: DamusState, post: NSMutableAttributedString, action: Post case .highlighting(let draft): break } - - // include pubkeys - tags += pubkeys.map { pk in - ["p", pk.hex()] - } // append additional tags tags += uploadedMedias.compactMap { $0.metadata?.to_tag() } @@ -717,9 +712,14 @@ func build_post(state: DamusState, post: NSMutableAttributedString, action: Post if !(content.isEmpty || content.allSatisfy { $0.isWhitespace }) { tags.append(["comment", content]) } + tags += pubkeys.map { pk in + ["p", pk.hex(), "mention"] + } return NostrPost(content: draft.selected_text, kind: .highlight, tags: tags) default: - break + tags += pubkeys.map { pk in + ["p", pk.hex()] + } } return NostrPost(content: content, kind: .text, tags: tags) From cf16a9cd1090156a363fe875d2a7bacf55b6f006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Fri, 30 Aug 2024 09:57:09 -0700 Subject: [PATCH 07/10] Improve handling of NostrDB when switching apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was an issue where profiles on Damus would not load when switching back and forth between the extension and Damus. This commit fixes that by closing NostrDB when the extension is backgrounded Testing ------- PASS Device: iPhone 13 Mini iOS: 17.6.1 Damus: This commit Steps: 1. Go to a webpage in safari, and open the highlight extension 2. With the highlight extension open, switch apps to Damus (without closing the extension) 3. Make sure profiles can be loaded on Damus Signed-off-by: Daniel D’Aquino --- .../ActionViewController.swift | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/highlighter action extension/ActionViewController.swift b/highlighter action extension/ActionViewController.swift index a1ef5c46..72aa1ea9 100644 --- a/highlighter action extension/ActionViewController.swift +++ b/highlighter action extension/ActionViewController.swift @@ -20,6 +20,8 @@ struct ShareExtensionView: View { @State private var selectedTextHeight: CGFloat = .zero @State private var selectedTextWidth: CGFloat = .zero + @Environment(\.scenePhase) var scenePhase + var body: some View { VStack(spacing: 15) { if let state { @@ -147,6 +149,41 @@ struct ShareExtensionView: View { self.highlighter_state = .cancelled } } + .onChange(of: scenePhase) { (phase: ScenePhase) in + guard let state else { return } + switch phase { + case .background: + print("txn: 📙 HIGHLIGHTER BACKGROUNDED") + Task { @MainActor in + state.ndb.close() + } + break + case .inactive: + print("txn: 📙 HIGHLIGHTER INACTIVE") + break + case .active: + print("txn: 📙 HIGHLIGHTER ACTIVE") + state.pool.ping() + @unknown default: + break + } + } + .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { obj in + guard let state else { return } + print("txn: 📙 HIGHLIGHTER ACTIVE NOTIFY") + if state.ndb.reopen() { + print("txn: HIGHLIGHTER NOSTRDB REOPENED") + } else { + print("txn: HIGHLIGHTER NOSTRDB FAILED TO REOPEN closed: \(state.ndb.is_closed)") + } + } + .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { obj in + guard let state else { return } + print("txn: 📙 HIGHLIGHTER BACKGROUNDED") + Task { @MainActor in + state.ndb.close() + } + } } func loadSharedUrl() { From 5d2fc0ed544af1fbb6ae7899a968bd280cab518a Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sun, 1 Sep 2024 06:20:11 -0700 Subject: [PATCH 08/10] lmdb: patch semaphore names to use group container prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is an attempt to fix various issues when acquiring a IPC semaphore on iOS See: https://github.com/damus-io/damus/issues/2323#issuecomment-2323181204 Running this patch gives us these names: mdb_env_setup_locks: using semnames 'group.com.damus/MDBrwDDi_FHxD' (29), 'group.com.damus/MDBwwDDi_FHxD' (29) From old Apple docs: > IPC and POSIX Semaphores and Shared Memory > > Normally, sandboxed apps cannot use Mach IPC, POSIX semaphores and > shared memory, or UNIX domain sockets (usefully). However, by specifying > an entitlement that requests membership in an application group, an app > can use these technologies to communicate with other members of that > application group. > > Note: System V semaphores are not supported in sandboxed apps. > > UNIX domain sockets are straightforward; they work just like any other > file. > > Any semaphore or Mach port that you wish to access within a sandboxed > app must be named according to a special convention: > > POSIX semaphores and shared memory names must begin with the application > group identifier, followed by a slash (/), followed by a name of your > choosing. > > Mach port names must begin with the application group identifier, > followed by a period (.), followed by a name of your choosing. > > For example, if your application group’s name is > Z123456789.com.example.app-group, you might create two semaphores named > Z123456789.myappgroup/rdyllwflg and Z123456789.myappgroup/bluwhtflg. You > might create a Mach port named > Z123456789.com.example.app-group.Port_of_Kobe. > > Note: The maximum length of a POSIX semaphore name is only 31 bytes, so > if you need to use POSIX semaphores, you should keep your app group > names short. Link: https://github.com/damus-io/damus/issues/2323#issuecomment-2323305949 Signed-off-by: William Casarin --- damus.xcodeproj/project.pbxproj | 7 ++++++- nostrdb/mdb.c | 13 +++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index adfb6c9f..9b096ad4 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -5034,6 +5034,8 @@ GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", + "MDB_SHORT_SEMNAMES=1", + "MDB_SEM_NAME_PREFIX=\"group.com.damus\"", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -5097,7 +5099,10 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; - "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ""; + GCC_PREPROCESSOR_DEFINITIONS = ( + "MDB_SHORT_SEMNAMES=1", + "MDB_SEM_NAME_PREFIX=\"group.com.damus\"", + ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; diff --git a/nostrdb/mdb.c b/nostrdb/mdb.c index deb67796..c9614a30 100644 --- a/nostrdb/mdb.c +++ b/nostrdb/mdb.c @@ -4893,8 +4893,17 @@ mdb_env_setup_locks(MDB_env *env, MDB_name *fname, int mode, int *excl) #ifdef MDB_SHORT_SEMNAMES encbuf[9] = '\0'; /* drop name from 15 chars to 14 chars */ #endif - sprintf(env->me_txns->mti_rmname, "/MDBr%s", encbuf); - sprintf(env->me_txns->mti_wmname, "/MDBw%s", encbuf); + +#define DEF_STR(x) #x +#define DEF_TO_STRING(x) DEF_STR(x) + sprintf(env->me_txns->mti_rmname, DEF_TO_STRING(MDB_SEM_NAME_PREFIX) "/MDBr%s", encbuf); + sprintf(env->me_txns->mti_wmname, DEF_TO_STRING(MDB_SEM_NAME_PREFIX) "/MDBw%s", encbuf); +#undef DEF_STR +#undef DEF_TO_STRING + + printf("mdb_env_setup_locks: using semnames '%s' (%d), '%s' (%d)\n", + env->me_txns->mti_rmname, strlen(env->me_txns->mti_rmname), + env->me_txns->mti_wmname, strlen(env->me_txns->mti_wmname)); /* Clean up after a previous run, if needed: Try to * remove both semaphores before doing anything else. */ From 1279791d65492feefa49b25237d849f208b2b06b Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sun, 1 Sep 2024 06:36:14 -0700 Subject: [PATCH 09/10] notifications: add extended virtual addressing entitlement It looks like our push notification service was missing the extended virtual memory entitlement. This is required to open nostrdb databases. Signed-off-by: William Casarin --- DamusNotificationService/DamusNotificationService.entitlements | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DamusNotificationService/DamusNotificationService.entitlements b/DamusNotificationService/DamusNotificationService.entitlements index b5d14b53..7f6ba390 100644 --- a/DamusNotificationService/DamusNotificationService.entitlements +++ b/DamusNotificationService/DamusNotificationService.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.kernel.extended-virtual-addressing + com.apple.security.app-sandbox com.apple.security.application-groups From 0cc9fc1670c4e314e0897c62a6314da6f6c85bc3 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sun, 1 Sep 2024 06:44:34 -0700 Subject: [PATCH 10/10] highlighter: add extended virtual addressing entitlement This is needed for opening nostrdb Signed-off-by: William Casarin --- .../highlighter action extension.entitlements | 2 ++ 1 file changed, 2 insertions(+) diff --git a/highlighter action extension/highlighter action extension.entitlements b/highlighter action extension/highlighter action extension.entitlements index d4753959..a917e809 100644 --- a/highlighter action extension/highlighter action extension.entitlements +++ b/highlighter action extension/highlighter action extension.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.kernel.extended-virtual-addressing + com.apple.security.app-sandbox com.apple.security.application-groups