WIP Fix mentions when writing notes
This commit is contained in:
@@ -16,6 +16,7 @@ let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Tex
|
|||||||
|
|
||||||
struct PostView: View {
|
struct PostView: View {
|
||||||
@State var post: NSMutableAttributedString = NSMutableAttributedString()
|
@State var post: NSMutableAttributedString = NSMutableAttributedString()
|
||||||
|
@State var cursor: Int = 0
|
||||||
@FocusState var focus: Bool
|
@FocusState var focus: Bool
|
||||||
@State var showPrivateKeyWarning: Bool = false
|
@State var showPrivateKeyWarning: Bool = false
|
||||||
@State var attach_media: Bool = false
|
@State var attach_media: Bool = false
|
||||||
@@ -104,7 +105,7 @@ struct PostView: View {
|
|||||||
|
|
||||||
var TextEntry: some View {
|
var TextEntry: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
TextViewWrapper(attributedText: $post)
|
TextViewWrapper(attributedText: $post, cursor: $cursor)
|
||||||
.focused($focus)
|
.focused($focus)
|
||||||
.textInputAutocapitalization(.sentences)
|
.textInputAutocapitalization(.sentences)
|
||||||
.onChange(of: post) { _ in
|
.onChange(of: post) { _ in
|
||||||
@@ -191,7 +192,7 @@ struct PostView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
let searching = get_searching_string(post.string)
|
let searching = get_searching_string(post.string, cursor: cursor)
|
||||||
|
|
||||||
TopBar
|
TopBar
|
||||||
|
|
||||||
@@ -204,7 +205,7 @@ struct PostView: View {
|
|||||||
|
|
||||||
// This if-block observes @ for tagging
|
// This if-block observes @ for tagging
|
||||||
if let searching {
|
if let searching {
|
||||||
UserSearch(damus_state: damus_state, search: searching, post: $post)
|
UserSearch(damus_state: damus_state, search: searching, post: $post, cursor: $cursor)
|
||||||
.frame(maxHeight: .infinity)
|
.frame(maxHeight: .infinity)
|
||||||
} else {
|
} else {
|
||||||
Divider()
|
Divider()
|
||||||
@@ -253,8 +254,12 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_searching_string(_ post: String) -> String? {
|
func get_searching_string(_ post: String, cursor: Int) -> String? {
|
||||||
guard let last_word = post.components(separatedBy: .whitespacesAndNewlines).last else {
|
guard cursor > 0 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let last_word = post[...post.index(post.startIndex, offsetBy: cursor - 1)].components(separatedBy: .whitespacesAndNewlines).last else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct UserSearch: View {
|
|||||||
let search: String
|
let search: String
|
||||||
|
|
||||||
@Binding var post: NSMutableAttributedString
|
@Binding var post: NSMutableAttributedString
|
||||||
|
@Binding var cursor: Int
|
||||||
|
|
||||||
var users: [SearchedUser] {
|
var users: [SearchedUser] {
|
||||||
guard let contacts = damus_state.contacts.event else {
|
guard let contacts = damus_state.contacts.event else {
|
||||||
@@ -36,38 +37,57 @@ struct UserSearch: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all characters after the last '@'
|
// Remove all characters after the '@' and before the cursor
|
||||||
removeCharactersAfterLastAtSymbol()
|
let newCursor = removeCharactersAfterAtSymbol()
|
||||||
|
|
||||||
// Create and append the user tag
|
// Create and append the user tag
|
||||||
let tagAttributedString = createUserTag(for: user, with: pk)
|
let tagAttributedString = createUserTag(for: user, with: pk)
|
||||||
appendUserTag(tagAttributedString)
|
insertUserTag(tagAttributedString, cursor: newCursor)
|
||||||
|
|
||||||
|
cursor = newCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
private func removeCharactersAfterLastAtSymbol() {
|
private func removeCharactersAfterAtSymbol() -> Int {
|
||||||
while post.string.last != "@" {
|
let newCursor = cursor
|
||||||
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
|
||||||
|
guard newCursor > 0 else {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
|
||||||
|
var atSymbolOffset = newCursor
|
||||||
|
while atSymbolOffset > 0 && post.string[post.string.index(post.string.startIndex, offsetBy: atSymbolOffset - 1)] != "@" {
|
||||||
|
atSymbolOffset -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var endOfWordOffset = newCursor
|
||||||
|
while endOfWordOffset < post.string.count && !post.string[post.string.index(post.string.startIndex, offsetBy: endOfWordOffset)].isWhitespace {
|
||||||
|
endOfWordOffset += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post.deleteCharacters(in: NSRange(location: atSymbolOffset - 1, length: endOfWordOffset - atSymbolOffset + 1))
|
||||||
|
|
||||||
|
return atSymbolOffset - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createUserTag(for user: SearchedUser, with pk: String) -> NSMutableAttributedString {
|
private func createUserTag(for user: SearchedUser, with pk: String) -> NSMutableAttributedString {
|
||||||
let name = Profile.displayName(profile: user.profile, pubkey: pk).username
|
let name = Profile.displayName(profile: user.profile, pubkey: pk).username
|
||||||
let tagString = "@\(name)\u{200B} "
|
let tagString = "\u{200B}@\(name)\u{200B} "
|
||||||
|
|
||||||
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
||||||
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
||||||
NSAttributedString.Key.link: "@\(pk)"])
|
NSAttributedString.Key.link: "@\(pk)"])
|
||||||
|
tagAttributedString.removeAttribute(.link, range: NSRange(location: 0, length: 1))
|
||||||
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||||
|
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: 0, length: 1))
|
||||||
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||||
|
|
||||||
return tagAttributedString
|
return tagAttributedString
|
||||||
}
|
}
|
||||||
|
|
||||||
private func appendUserTag(_ tagAttributedString: NSMutableAttributedString) {
|
private func insertUserTag(_ tagAttributedString: NSMutableAttributedString, cursor: Int) {
|
||||||
let mutableString = NSMutableAttributedString()
|
let mutableString = NSMutableAttributedString()
|
||||||
mutableString.append(post)
|
mutableString.append(post)
|
||||||
mutableString.append(tagAttributedString)
|
mutableString.insert(tagAttributedString, at: cursor)
|
||||||
post = mutableString
|
post = mutableString
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,9 +108,10 @@ struct UserSearch: View {
|
|||||||
struct UserSearch_Previews: PreviewProvider {
|
struct UserSearch_Previews: PreviewProvider {
|
||||||
static let search: String = "jb55"
|
static let search: String = "jb55"
|
||||||
@State static var post: NSMutableAttributedString = NSMutableAttributedString(string: "some @jb55")
|
@State static var post: NSMutableAttributedString = NSMutableAttributedString(string: "some @jb55")
|
||||||
|
@State static var cursor: Int = 0
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
UserSearch(damus_state: test_damus_state(), search: search, post: $post)
|
UserSearch(damus_state: test_damus_state(), search: search, post: $post, cursor: $cursor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct TextViewWrapper: UIViewRepresentable {
|
struct TextViewWrapper: UIViewRepresentable {
|
||||||
@Binding var attributedText: NSMutableAttributedString
|
@Binding var attributedText: NSMutableAttributedString
|
||||||
|
@Binding var cursor: Int
|
||||||
|
|
||||||
func makeUIView(context: Context) -> UITextView {
|
func makeUIView(context: Context) -> UITextView {
|
||||||
let textView = UITextView()
|
let textView = UITextView()
|
||||||
@@ -31,18 +32,21 @@ struct TextViewWrapper: UIViewRepresentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
func makeCoordinator() -> Coordinator {
|
||||||
Coordinator(attributedText: $attributedText)
|
Coordinator(attributedText: $attributedText, cursor: $cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Coordinator: NSObject, UITextViewDelegate {
|
class Coordinator: NSObject, UITextViewDelegate {
|
||||||
@Binding var attributedText: NSMutableAttributedString
|
@Binding var attributedText: NSMutableAttributedString
|
||||||
|
@Binding var cursor: Int
|
||||||
|
|
||||||
init(attributedText: Binding<NSMutableAttributedString>) {
|
init(attributedText: Binding<NSMutableAttributedString>, cursor: Binding<Int>) {
|
||||||
_attributedText = attributedText
|
_attributedText = attributedText
|
||||||
|
_cursor = cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
func textViewDidChange(_ textView: UITextView) {
|
func textViewDidChange(_ textView: UITextView) {
|
||||||
attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
|
attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
|
||||||
|
cursor = textView.selectedRange.upperBound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user