commit c35331ceda3f12f6d15ffea0a5672197ed399d23
parent 88ddb70ca821e9bfa803d898a988d167c28b37d9
Author: Swift <scoder1747@gmail.com>
Date: Sat, 6 May 2023 23:12:59 -0400
Fix bug where you could only mention users at the end of a post
Changelog-Fixed: Fix bug where you could only mention users at the end of a post
Closes: #1102
Diffstat:
3 files changed, 70 insertions(+), 25 deletions(-)
diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift
@@ -43,6 +43,8 @@ struct PostView: View {
@State var image_upload_confirm: Bool = false
@State var originalReferences: [ReferencedId] = []
@State var references: [ReferencedId] = []
+ @State var focusWordAttributes: (String?, NSRange?) = (nil, nil)
+ @State var newCursorIndex: Int?
@State var mediaToUpload: MediaUpload? = nil
@@ -203,7 +205,10 @@ struct PostView: View {
var TextEntry: some View {
ZStack(alignment: .topLeading) {
- TextViewWrapper(attributedText: $post)
+ TextViewWrapper(attributedText: $post, cursorIndex: newCursorIndex, getFocusWordForMention: { word, range in
+ focusWordAttributes = (word, range)
+ self.newCursorIndex = nil
+ })
.focused($focus)
.textInputAutocapitalization(.sentences)
.onChange(of: post) { p in
@@ -312,8 +317,7 @@ struct PostView: View {
var body: some View {
GeometryReader { (deviceSize: GeometryProxy) in
VStack(alignment: .leading, spacing: 0) {
-
- let searching = get_searching_string(post.string)
+ let searching = get_searching_string(focusWordAttributes.0)
TopBar
@@ -333,7 +337,7 @@ struct PostView: View {
// This if-block observes @ for tagging
if let searching {
- UserSearch(damus_state: damus_state, search: searching, post: $post)
+ UserSearch(damus_state: damus_state, search: searching, focusWordAttributes: $focusWordAttributes, newCursorIndex: $newCursorIndex, post: $post)
.frame(maxHeight: .infinity)
} else {
Divider()
@@ -412,25 +416,26 @@ struct PostView: View {
}
}
-func get_searching_string(_ post: String) -> String? {
- guard let last_word = post.components(separatedBy: .whitespacesAndNewlines).last else {
+func get_searching_string(_ word: String?) -> String? {
+ guard let word = word else {
return nil
}
-
- guard last_word.count >= 2 else {
+
+ guard word.count >= 2 else {
return nil
}
- guard last_word.first! == "@" else {
+ guard let firstCharacter = word.first,
+ firstCharacter == "@" else {
return nil
}
// don't include @npub... strings
- guard last_word.count != 64 else {
+ guard word.count != 64 else {
return nil
}
- return String(last_word.dropFirst())
+ return String(word.dropFirst())
}
struct PostView_Previews: PreviewProvider {
diff --git a/damus/Views/Posting/UserSearch.swift b/damus/Views/Posting/UserSearch.swift
@@ -20,6 +20,8 @@ struct SearchedUser: Identifiable {
struct UserSearch: View {
let damus_state: DamusState
let search: String
+ @Binding var focusWordAttributes: (String?, NSRange?)
+ @Binding var newCursorIndex: Int?
@Binding var post: NSMutableAttributedString
@@ -35,20 +37,19 @@ struct UserSearch: View {
guard let pk = bech32_pubkey(user.pubkey) else {
return
}
-
- // Remove all characters after the last '@'
- removeCharactersAfterLastAtSymbol()
-
- // Create and append the user tag
let tagAttributedString = createUserTag(for: user, with: pk)
- appendUserTag(tagAttributedString)
+ appendUserTag(withTag: tagAttributedString)
}
-
- private func removeCharactersAfterLastAtSymbol() {
- while post.string.last != "@" {
- post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
+
+ private func appendUserTag(withTag tagAttributedString: NSMutableAttributedString) {
+ guard let wordRange = focusWordAttributes.1 else {
+ return
}
- post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
+ let mutableString = NSMutableAttributedString(attributedString: post)
+ mutableString.replaceCharacters(in: wordRange, with: tagAttributedString)
+ post = mutableString
+ focusWordAttributes = (nil, nil)
+ newCursorIndex = wordRange.location + tagAttributedString.string.count
}
private func createUserTag(for user: SearchedUser, with pk: String) -> NSMutableAttributedString {
@@ -97,9 +98,11 @@ struct UserSearch: View {
struct UserSearch_Previews: PreviewProvider {
static let search: String = "jb55"
@State static var post: NSMutableAttributedString = NSMutableAttributedString(string: "some @jb55")
+ @State static var word: (String?, NSRange?) = (nil, nil)
+ @State static var newCursorIndex: Int?
static var previews: some View {
- UserSearch(damus_state: test_damus_state(), search: search, post: $post)
+ UserSearch(damus_state: test_damus_state(), search: search, focusWordAttributes: $word, newCursorIndex: $newCursorIndex, post: $post)
}
}
diff --git a/damus/Views/TextViewWrapper.swift b/damus/Views/TextViewWrapper.swift
@@ -9,6 +9,8 @@ import SwiftUI
struct TextViewWrapper: UIViewRepresentable {
@Binding var attributedText: NSMutableAttributedString
+ let cursorIndex: Int?
+ var getFocusWordForMention: ((String?, NSRange?) -> Void)? = nil
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
@@ -29,21 +31,56 @@ struct TextViewWrapper: UIViewRepresentable {
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = attributedText
TextViewWrapper.setTextProperties(uiView)
+ setCursorPosition(textView: uiView)
+ }
+
+ private func setCursorPosition(textView: UITextView) {
+ guard let index = cursorIndex, let newPosition = textView.position(from: textView.beginningOfDocument, offset: index) else {
+ return
+ }
+ textView.selectedTextRange = textView.textRange(from: newPosition, to: newPosition)
}
func makeCoordinator() -> Coordinator {
- Coordinator(attributedText: $attributedText)
+ Coordinator(attributedText: $attributedText, getFocusWordForMention: getFocusWordForMention)
}
class Coordinator: NSObject, UITextViewDelegate {
@Binding var attributedText: NSMutableAttributedString
+ var getFocusWordForMention: ((String?, NSRange?) -> Void)? = nil
- init(attributedText: Binding<NSMutableAttributedString>) {
+ init(attributedText: Binding<NSMutableAttributedString>, getFocusWordForMention: ((String?, NSRange?) -> Void)?) {
_attributedText = attributedText
+ self.getFocusWordForMention = getFocusWordForMention
}
func textViewDidChange(_ textView: UITextView) {
attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
+ processFocusedWordForMention(textView: textView)
+ }
+
+ private func processFocusedWordForMention(textView: UITextView) {
+ if let selectedRange = textView.selectedTextRange {
+ var val: (String?, NSRange?)
+ if let wordRange = textView.tokenizer.rangeEnclosingPosition(selectedRange.start, with: .word, inDirection: .init(rawValue: UITextLayoutDirection.left.rawValue)) {
+ if let startPosition = textView.position(from: wordRange.start, offset: -1),
+ let cursorPosition = textView.position(from: selectedRange.start, offset: 0) {
+ let word = textView.text(in: textView.textRange(from: startPosition, to: cursorPosition)!)
+ val = (word, convertToNSRange(startPosition, cursorPosition, textView))
+ }
+ }
+ getFocusWordForMention?(val.0, val.1)
+ }
+ }
+
+ private func convertToNSRange( _ startPosition: UITextPosition, _ endPosition: UITextPosition, _ textView: UITextView) -> NSRange? {
+ let startOffset = textView.offset(from: textView.beginningOfDocument, to: startPosition)
+ let endOffset = textView.offset(from: textView.beginningOfDocument, to: endPosition)
+ let length = endOffset - startOffset
+ guard length >= 0, startOffset >= 0 else {
+ return nil
+ }
+ return NSRange(location: startOffset, length: length)
}
}
}