damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit e9be227009873bb4c5b28c2e9cb96c91b29f2997
parent 2e640db0129f07d24e409edc00873cf2c96eb845
Author: William Casarin <jb55@jb55.com>
Date:   Thu,  6 Apr 2023 16:04:16 -0700

Add bitcoin icon to bitcoin hashtags

Changelog-Added: Add bitcoin icon to bitcoin hashtags

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 8++++++++
Adamus/Assets.xcassets/bitcoin-hashtag.imageset/Contents.json | 23+++++++++++++++++++++++
Adamus/Assets.xcassets/bitcoin-hashtag.imageset/bitcoin-hashtag.svg | 43+++++++++++++++++++++++++++++++++++++++++++
Mdamus/Components/TranslateView.swift | 4++--
Adamus/Components/TruncatedText.swift | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Models/HomeModel.swift | 2+-
Adamus/Util/CompatibleAttribute.swift | 44++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/NoteContentView.swift | 95+++++++++++++++++++++++++++++++++++++++++--------------------------------------
8 files changed, 223 insertions(+), 49 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -136,6 +136,8 @@ 4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */; }; 4C7FF7D52823313F009601DB /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; }; 4C8682872814DE470026224F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; }; + 4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */; }; + 4C8D00CA29DF80350036AF10 /* TruncatedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00C929DF80350036AF10 /* TruncatedText.swift */; }; 4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; }; 4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; }; 4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; }; @@ -535,6 +537,8 @@ 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = "<group>"; }; 4C7FF7D42823313F009601DB /* Mentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mentions.swift; sourceTree = "<group>"; }; 4C8682862814DE470026224F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; }; + 4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibleAttribute.swift; sourceTree = "<group>"; }; + 4C8D00C929DF80350036AF10 /* TruncatedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedText.swift; sourceTree = "<group>"; }; 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusColors.swift; sourceTree = "<group>"; }; 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; }; 4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; }; @@ -983,6 +987,7 @@ 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */, 4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */, 4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */, + 4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */, ); path = Util; sourceTree = "<group>"; @@ -1102,6 +1107,7 @@ 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */, 4CE4F0F729DB7399005914DB /* ThiccDivider.swift */, 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */, + 4C8D00C929DF80350036AF10 /* TruncatedText.swift */, ); path = Components; sourceTree = "<group>"; @@ -1487,6 +1493,7 @@ 4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */, 4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */, 4C75EFB92804A2740006080F /* EventView.swift in Sources */, + 4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */, 3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */, F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */, 4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */, @@ -1513,6 +1520,7 @@ 4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */, 4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */, F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */, + 4C8D00CA29DF80350036AF10 /* TruncatedText.swift in Sources */, 4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */, 4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */, 4C649844285A952100EAE2B3 /* LocalUserConfig.swift in Sources */, diff --git a/damus/Assets.xcassets/bitcoin-hashtag.imageset/Contents.json b/damus/Assets.xcassets/bitcoin-hashtag.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "bitcoin-hashtag.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bitcoin-hashtag.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bitcoin-hashtag.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/damus/Assets.xcassets/bitcoin-hashtag.imageset/bitcoin-hashtag.svg b/damus/Assets.xcassets/bitcoin-hashtag.imageset/bitcoin-hashtag.svg @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="9.5679588" + height="12.664063" + viewBox="0 0 9.5679588 12.664063" + version="1.1" + id="svg2" + sodipodi:docname="bitcoin-hashtag.svg" + inkscape:version="1.3-dev (77bc73e, 2022-05-18)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs2" /> + <sodipodi:namedview + id="namedview2" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="31.12" + inkscape:cx="4.2095116" + inkscape:cy="7.4710797" + inkscape:window-width="1571" + inkscape:window-height="957" + inkscape:window-x="1707" + inkscape:window-y="66" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <g + id="surface1" + transform="translate(-4.867188,-3.484375)"> + <path + style="fill:#f59119;fill-opacity:1;fill-rule:nonzero;stroke:none" + d="M 14.410156,8.574219 C 14.609375,7.246094 13.59375,6.53125 12.210938,6.050781 L 12.660156,4.25 11.5625,3.976562 11.125,5.730469 C 10.835938,5.660156 10.539062,5.589844 10.246094,5.523438 L 10.6875,3.757812 9.589844,3.484375 9.140625,5.285156 C 8.902344,5.230469 8.667969,5.179688 8.4375,5.121094 L 8.441406,5.117188 6.925781,4.738281 6.636719,5.910156 c 0,0 0.8125,0.1875 0.796875,0.199219 C 7.875,6.21875 7.957031,6.511719 7.941406,6.746094 L 7.429688,8.800781 C 7.460938,8.808594 7.5,8.820312 7.546875,8.835938 l -0.117187,-0.027344 -0.71875,2.875 c -0.054688,0.136718 -0.191407,0.339844 -0.5,0.261718 0.00781,0.01563 -0.800782,-0.199218 -0.800782,-0.199218 L 4.867188,13 6.296875,13.359375 c 0.265625,0.06641 0.523437,0.132813 0.78125,0.199219 L 6.621094,15.382812 7.71875,15.65625 8.167969,13.851562 c 0.300781,0.08203 0.589843,0.15625 0.875,0.226563 L 8.59375,15.875 l 1.097656,0.273438 0.453125,-1.820313 c 1.871094,0.355469 3.28125,0.210937 3.871094,-1.480469 0.476563,-1.363281 -0.02344,-2.148437 -1.007813,-2.660156 0.71875,-0.167969 1.257813,-0.636719 1.402344,-1.613281 z m -2.507812,3.515625 C 11.5625,13.453125 9.269531,12.71875 8.523438,12.53125 l 0.605468,-2.414062 c 0.742188,0.183593 3.125,0.550781 2.773438,1.972656 z M 12.242188,8.554688 C 11.933594,9.796875 10.023438,9.164062 9.402344,9.011719 L 9.949219,6.820312 c 0.621093,0.15625 2.613281,0.441407 2.292969,1.734376 z m 0,0" + id="path2" /> + </g> +</svg> diff --git a/damus/Components/TranslateView.swift b/damus/Components/TranslateView.swift @@ -65,9 +65,9 @@ struct TranslateView: View { .padding([.top, .bottom], 10) if self.size == .selected { - SelectableText(attributedString: artifacts.content, size: self.size) + SelectableText(attributedString: artifacts.content.attributed, size: self.size) } else { - Text(artifacts.content) + artifacts.content.text .font(eventviewsize_to_font(self.size)) } } diff --git a/damus/Components/TruncatedText.swift b/damus/Components/TruncatedText.swift @@ -0,0 +1,53 @@ +// +// TruncatedText.swift +// damus +// +// Created by William Casarin on 2023-04-06. +// + +import SwiftUI + +struct TruncatedText: View { + let text: CompatibleText + let maxChars: Int = 280 + + var body: some View { + let truncatedAttributedString: AttributedString? = getTruncatedString() + + if let truncatedAttributedString { + Text(truncatedAttributedString) + .fixedSize(horizontal: false, vertical: true) + } else { + text.text + .fixedSize(horizontal: false, vertical: true) + } + + if truncatedAttributedString != nil { + Spacer() + Button(NSLocalizedString("Show more", comment: "Button to show entire note.")) { } + .allowsHitTesting(false) + } + } + + func getTruncatedString() -> AttributedString? { + let nsAttributedString = NSAttributedString(text.attributed) + if nsAttributedString.length < maxChars { return nil } + + let range = NSRange(location: 0, length: maxChars) + let truncatedAttributedString = nsAttributedString.attributedSubstring(from: range) + + return AttributedString(truncatedAttributedString) + "..." + } +} + +struct TruncatedText_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 100) { + TruncatedText(text: CompatibleText(stringLiteral: "hello\nthere\none\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven")) + .frame(width: 200, height: 200) + + TruncatedText(text: CompatibleText(stringLiteral: "hello\nthere\none\ntwo\nthree\nfour")) + .frame(width: 200, height: 200) + } + } +} diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift @@ -1011,7 +1011,7 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) { for block in ev.blocks(damus_state.keypair.privkey) { if case .mention(let mention) = block, mention.ref.ref_id == damus_state.keypair.pubkey, let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name { - let justContent = NSAttributedString(render_note_content(ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content).string + let justContent = NSAttributedString(render_note_content(ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string create_local_notification(displayName: displayName, conversation: justContent, type: type) } } diff --git a/damus/Util/CompatibleAttribute.swift b/damus/Util/CompatibleAttribute.swift @@ -0,0 +1,44 @@ +// +// CompatibleAttribute.swift +// damus +// +// Created by William Casarin on 2023-04-06. +// + +import Foundation +import SwiftUI + +class CompatibleText: Equatable { + var text: Text + var attributed: AttributedString + + init() { + self.text = Text("") + self.attributed = AttributedString(stringLiteral: "") + } + + init(stringLiteral: String) { + self.text = Text(stringLiteral) + self.attributed = AttributedString(stringLiteral: stringLiteral) + } + + init(text: Text, attributed: AttributedString) { + self.text = text + self.attributed = attributed + } + + init(attributed: AttributedString) { + self.text = Text(attributed) + self.attributed = attributed + } + + static func == (lhs: CompatibleText, rhs: CompatibleText) -> Bool { + return lhs.attributed == rhs.attributed + } + + static func +(lhs: CompatibleText, rhs: CompatibleText) -> CompatibleText { + let combinedText = lhs.text + rhs.text + let combinedAttributes = lhs.attributed + rhs.attributed + return CompatibleText(text: combinedText, attributed: combinedAttributes) + } +} diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift @@ -60,8 +60,15 @@ struct NoteContentView: View { } var truncatedText: some View { - TruncatedText(text: artifacts.content, maxChars: (truncate ? 280 : nil)) - .font(eventviewsize_to_font(size)) + Group { + if truncate { + TruncatedText(text: artifacts.content) + .font(eventviewsize_to_font(size)) + } else { + artifacts.content.text + .font(eventviewsize_to_font(size)) + } + } } var invoicesView: some View { @@ -92,10 +99,10 @@ struct NoteContentView: View { VStack(alignment: .leading) { if size == .selected { if with_padding { - SelectableText(attributedString: artifacts.content, size: self.size) + SelectableText(attributedString: artifacts.content.attributed, size: self.size) .padding(.horizontal) } else { - SelectableText(attributedString: artifacts.content, size: self.size) + SelectableText(attributedString: artifacts.content.attributed, size: self.size) } } else { if with_padding { @@ -198,27 +205,49 @@ struct NoteContentView: View { } } -func hashtag_str(_ htag: String) -> AttributedString { +enum ImageName { + case systemImage(String) + case image(String) +} + +func attributed_string_attach_icon(_ astr: inout AttributedString, img: UIImage) { + let attachment = NSTextAttachment() + attachment.image = img + let attachmentString = NSAttributedString(attachment: attachment) + let wrapped = AttributedString(attachmentString) + astr.append(wrapped) +} + +func hashtag_str(_ htag: String) -> CompatibleText { var attributedString = AttributedString(stringLiteral: "#\(htag)") attributedString.link = URL(string: "damus:t:\(htag)") + var text = Text(attributedString) + if htag.lowercased() == "bitcoin" { attributedString.foregroundColor = Color.orange + if let img = UIImage(named: "bitcoin-hashtag") { + attributedString = attributedString + " " + attributed_string_attach_icon(&attributedString, img: img) + } + let img = Image("bitcoin-hashtag") + text = text.foregroundColor(.orange) + Text(" \(img)") } else { attributedString.foregroundColor = DamusColors.purple } - return attributedString + return CompatibleText(text: text, attributed: attributedString) } -func url_str(_ url: URL) -> AttributedString { +func url_str(_ url: URL) -> CompatibleText { var attributedString = AttributedString(stringLiteral: url.absoluteString) attributedString.link = url attributedString.foregroundColor = DamusColors.purple - return attributedString + + return CompatibleText(attributed: attributedString) } -func mention_str(_ m: Mention, profiles: Profiles) -> AttributedString { +func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText { switch m.type { case .pubkey: let pk = m.ref.ref_id @@ -227,13 +256,15 @@ func mention_str(_ m: Mention, profiles: Profiles) -> AttributedString { var attributedString = AttributedString(stringLiteral: "@\(disp)") attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))") attributedString.foregroundColor = DamusColors.purple - return attributedString + + return CompatibleText(attributed: attributedString) case .event: let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))") attributedString.link = URL(string: "damus:\(encode_event_id_uri(m.ref))") attributedString.foregroundColor = DamusColors.purple - return attributedString + + return CompatibleText(attributed: attributedString) } } @@ -241,7 +272,8 @@ struct NoteContentView_Previews: PreviewProvider { static var previews: some View { let state = test_damus_state() let content = "hi there ¯\\_(ツ)_/¯ https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg" - let artifacts = NoteArtifacts(content: AttributedString(stringLiteral: content), images: [], invoices: [], links: []) + let txt = CompatibleText(attributed: AttributedString(stringLiteral: content)) + let artifacts = NoteArtifacts(content: txt, images: [], invoices: [], links: []) NoteContentView(damus_state: state, event: NostrEvent(content: content, pubkey: "pk"), show_images: true, size: .normal, artifacts: artifacts, options: []) } } @@ -251,13 +283,14 @@ struct NoteArtifacts: Equatable { return lhs.content == rhs.content } - let content: AttributedString + let content: CompatibleText let images: [URL] let invoices: [Invoice] let links: [URL] static func just_content(_ content: String) -> NoteArtifacts { - NoteArtifacts(content: AttributedString(stringLiteral: content), images: [], invoices: [], links: []) + let txt = CompatibleText(attributed: AttributedString(stringLiteral: content)) + return NoteArtifacts(content: txt, images: [], invoices: [], links: []) } } @@ -277,7 +310,7 @@ func render_blocks(blocks: [Block], profiles: Profiles, privkey: String?) -> Not .count == 1 var ind: Int = -1 - let txt: AttributedString = blocks.reduce("") { str, block in + let txt: CompatibleText = blocks.reduce(CompatibleText()) { str, block in ind = ind + 1 switch block { @@ -300,7 +333,7 @@ func render_blocks(blocks: [Block], profiles: Profiles, privkey: String?) -> Not } } - return str + AttributedString(stringLiteral: trimmed) + return str + CompatibleText(stringLiteral: trimmed) case .hashtag(let htag): return str + hashtag_str(htag) case .invoice(let invoice): @@ -349,36 +382,6 @@ func load_cached_preview(previews: PreviewCache, evid: String) -> LinkViewRepres return LinkViewRepresentable(meta: .linkmeta(meta)) } -struct TruncatedText: View { - - let text: AttributedString - let maxChars: Int? - - var body: some View { - let truncatedAttributedString: AttributedString? = getTruncatedString() - - Text(truncatedAttributedString ?? text) - .fixedSize(horizontal: false, vertical: true) - - if truncatedAttributedString != nil { - Spacer() - Button(NSLocalizedString("Show more", comment: "Button to show entire note.")) { } - .allowsHitTesting(false) - } - } - - func getTruncatedString() -> AttributedString? { - guard let maxChars = maxChars else { return nil } - let nsAttributedString = NSAttributedString(text) - if nsAttributedString.length < maxChars { return nil } - - let range = NSRange(location: 0, length: maxChars) - let truncatedAttributedString = nsAttributedString.attributedSubstring(from: range) - - return AttributedString(truncatedAttributedString) + "..." - } -} - // trim suffix whitespace and newlines func trim_suffix(_ str: String) -> String {