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
This commit is contained in:
Swift
2023-05-06 23:12:59 -04:00
committed by William Casarin
parent 88ddb70ca8
commit c35331ceda
3 changed files with 72 additions and 27 deletions

View File

@@ -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 word.count >= 2 else {
return nil
}
guard last_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 {

View File

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

View File

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