damus

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

commit a1a89dc98e54c9dfe30cc35267cbbf16c471a0a5
parent 3e764e75e4c3a8be9c006136bac1a3d09d34994e
Author: OlegAba <mail@olegaba.com>
Date:   Fri, 17 Feb 2023 12:34:41 -0500

Add selectable text feature

Changelog-Added: Added the ability to select text on posts
Closes: #639

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Adamus/Components/SelectableText.swift | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Components/TranslateView.swift | 7++-----
Mdamus/Views/ChatView.swift | 4++--
Mdamus/Views/DMView.swift | 2+-
Mdamus/Views/Events/EventBody.swift | 2+-
Mdamus/Views/NoteContentView.swift | 19++++++++++---------
7 files changed, 116 insertions(+), 18 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -209,6 +209,7 @@ 7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; }; 7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; }; 7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */; }; + 7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFF6316299FEFE5005D382A /* SelectableText.swift */; }; 9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; }; BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; }; BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; }; @@ -518,6 +519,7 @@ 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSVG.swift; sourceTree = "<group>"; }; 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; }; 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KFOptionSetter+.swift"; sourceTree = "<group>"; }; + 7CFF6316299FEFE5005D382A /* SelectableText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableText.swift; sourceTree = "<group>"; }; 9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; }; BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; }; BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; }; @@ -869,6 +871,7 @@ 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */, 4CB883AF297705DD00DC99E7 /* ZapButton.swift */, 4C42812B298C848200DBF26F /* TranslateView.swift */, + 7CFF6316299FEFE5005D382A /* SelectableText.swift */, ); path = Components; sourceTree = "<group>"; @@ -1338,6 +1341,7 @@ 4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */, 4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */, 4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */, + 7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */, 4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */, 4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */, 4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */, diff --git a/damus/Components/SelectableText.swift b/damus/Components/SelectableText.swift @@ -0,0 +1,96 @@ +// +// SelectableText.swift +// damus +// +// Created by Oleg Abalonski on 2/16/23. +// + +import UIKit +import SwiftUI + +struct SelectableText: View { + + let attributedString: AttributedString + + @State private var selectedTextHeight: CGFloat = .zero + @State private var selectedTextWidth: CGFloat = .zero + + var body: some View { + GeometryReader { geo in + TextViewRepresentable( + attributedString: attributedString, + textColor: UIColor.label, + font: UIFont.preferredFont(forTextStyle: .title2), + fixedWidth: selectedTextWidth, + height: $selectedTextHeight + ) + .onAppear { + self.selectedTextWidth = geo.size.width + } + .onChange(of: geo.size) { newSize in + self.selectedTextWidth = newSize.width + } + } + .frame(height: selectedTextHeight) + } +} + + fileprivate struct TextViewRepresentable: UIViewRepresentable { + + let attributedString: AttributedString + let textColor: UIColor + let font: UIFont + let fixedWidth: CGFloat + + @Binding var height: CGFloat + + func makeUIView(context: UIViewRepresentableContext<Self>) -> UITextView { + let view = UITextView() + view.isEditable = false + view.dataDetectorTypes = .all + view.isSelectable = true + view.textContainer.lineFragmentPadding = 0 + view.textContainerInset = .zero + return view + } + + func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<Self>) { + let mutableAttributedString = createNSAttributedString() + uiView.attributedText = mutableAttributedString + + let newHeight = mutableAttributedString.height(containerWidth: fixedWidth) + + DispatchQueue.main.async { + height = newHeight + } + } + + func createNSAttributedString() -> NSMutableAttributedString { + let mutableAttributedString = NSMutableAttributedString(attributedString) + let myAttribute = [ + NSAttributedString.Key.font: font, + NSAttributedString.Key.foregroundColor: textColor + ] + + mutableAttributedString.addAttributes( + myAttribute, + range: NSRange.init(location: 0, length: mutableAttributedString.length) + ) + + return mutableAttributedString + } +} + +fileprivate extension NSAttributedString { + + func height(containerWidth: CGFloat) -> CGFloat { + + let rect = self.boundingRect( + with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude), + options: [.usesLineFragmentOrigin, .usesFontLeading], + context: nil + ) + + return ceil(rect.size.height) + } +} diff --git a/damus/Components/TranslateView.swift b/damus/Components/TranslateView.swift @@ -11,7 +11,6 @@ import NaturalLanguage struct TranslateView: View { let damus_state: DamusState let event: NostrEvent - let size: EventViewKind @State var checkingTranslationStatus: Bool = false @State var currentLanguage: String = "en" @@ -34,9 +33,7 @@ struct TranslateView: View { } .translate_button_style() - Text(artifacts.content) - .font(eventviewsize_to_font(size)) - .fixedSize(horizontal: false, vertical: true) + SelectableText(attributedString: artifacts.content) } } @@ -143,6 +140,6 @@ struct TranslateView: View { struct TranslateView_Previews: PreviewProvider { static var previews: some View { let ds = test_damus_state() - TranslateView(damus_state: ds, event: test_event, size: .selected) + TranslateView(damus_state: ds, event: test_event) } } diff --git a/damus/Views/ChatView.swift b/damus/Views/ChatView.swift @@ -112,8 +112,8 @@ struct ChatView: View { NoteContentView(damus_state: damus_state, event: event, show_images: show_images, - artifacts: .just_content(event.content), - size: .normal) + size: .normal, + artifacts: .just_content(event.content)) if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey { let bar = make_actionbar_model(ev: event.id, damus: damus_state) diff --git a/damus/Views/DMView.swift b/damus/Views/DMView.swift @@ -23,7 +23,7 @@ struct DMView: View { let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey) - NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal) + NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: .normal, artifacts: .just_content(event.get_content(damus_state.keypair.privkey))) .foregroundColor(is_ours ? Color.white : Color.primary) .padding(10) .background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15)) diff --git a/damus/Views/Events/EventBody.swift b/damus/Views/Events/EventBody.swift @@ -23,7 +23,7 @@ struct EventBody: View { let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey, booster_pubkey: nil) - NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, artifacts: .just_content(content), size: size) + NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content)) .frame(maxWidth: .infinity, alignment: .leading) } } diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift @@ -22,24 +22,25 @@ struct Blur: UIViewRepresentable { } struct NoteContentView: View { + let damus_state: DamusState let event: NostrEvent let show_images: Bool + let size: EventViewKind @State var artifacts: NoteArtifacts - - let size: EventViewKind - @State var preview: LinkViewRepresentable? = nil func MainContent() -> some View { return VStack(alignment: .leading) { - Text(artifacts.content) - .font(eventviewsize_to_font(size)) - .fixedSize(horizontal: false, vertical: true) - + if size == .selected { - TranslateView(damus_state: damus_state, event: event, size: size) + SelectableText(attributedString: artifacts.content) + TranslateView(damus_state: damus_state, event: event) + } else { + Text(artifacts.content) + .font(eventviewsize_to_font(size)) + .fixedSize(horizontal: false, vertical: true) } if show_images && artifacts.images.count > 0 { @@ -166,7 +167,7 @@ struct NoteContentView_Previews: PreviewProvider { 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: []) - NoteContentView(damus_state: state, event: NostrEvent(content: content, pubkey: "pk"), show_images: true, artifacts: artifacts, size: .normal) + NoteContentView(damus_state: state, event: NostrEvent(content: content, pubkey: "pk"), show_images: true, size: .normal, artifacts: artifacts) } }