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:
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)
}
}