commit b6b6d033a8775b44a71a74c457f4fc5c3e211304
parent 819d7496b23d6b58006bd8b946ea51a5b507803d
Author: Swift <scoder1747@gmail.com>
Date: Sun, 22 Jan 2023 13:56:14 -0500
User tagging and autocompletion
Co-authored-by: William Casarin <jb55@jb55.com>
Changelog-Added: User tagging and autocompletion in posts
Closes: #347
Fixes: #411, #63
Diffstat:
5 files changed, 133 insertions(+), 4 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -171,6 +171,7 @@
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */; };
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; };
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; };
+ 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABF52985CD5500D66079 /* UserSearch.swift */; };
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
@@ -426,6 +427,7 @@
4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = "<group>"; };
4CF0ABED29844B5500D66079 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
4CF0ABEF29857E9200D66079 /* Bech32Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Object.swift; sourceTree = "<group>"; };
+ 4CF0ABF52985CD5500D66079 /* UserSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSearch.swift; sourceTree = "<group>"; };
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
@@ -587,6 +589,7 @@
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
+ 4CF0ABF42985CD4200D66079 /* Posting */,
4CF0ABDF2981A83000D66079 /* Muting */,
4CC7AAEE297F11B300430951 /* Events */,
3AA24800297E3DAE0090C62D /* Reposts */,
@@ -846,6 +849,14 @@
path = AnyCodable;
sourceTree = "<group>";
};
+ 4CF0ABF42985CD4200D66079 /* Posting */ = {
+ isa = PBXGroup;
+ children = (
+ 4CF0ABF52985CD5500D66079 /* UserSearch.swift */,
+ );
+ path = Posting;
+ sourceTree = "<group>";
+ };
F7F0BA23297892AE009531F3 /* Modifiers */ = {
isa = PBXGroup;
children = (
@@ -1014,6 +1025,7 @@
4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */,
+ 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
4C363AA828297703006E126D /* InsertSort.swift in Sources */,
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */,
4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */,
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -302,7 +302,7 @@ struct ContentView: View {
case .report(let target):
MaybeReportView(target: target)
case .post:
- PostView(replying_to: nil, references: [])
+ PostView(replying_to: nil, references: [], damus_state: damus_state!)
case .reply(let event):
ReplyView(replying_to: event, damus: damus_state!)
}
diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift
@@ -16,10 +16,11 @@ let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Tex
struct PostView: View {
@State var post: String = ""
-
- let replying_to: NostrEvent?
@FocusState var focus: Bool
+
+ let replying_to: NostrEvent?
let references: [ReferencedId]
+ let damus_state: DamusState
@Environment(\.presentationMode) var presentationMode
@@ -74,6 +75,7 @@ struct PostView: View {
TextEditor(text: $post)
.focused($focus)
.textInputAutocapitalization(.sentences)
+
if post.isEmpty {
Text(POST_PLACEHOLDER)
.padding(.top, 8)
@@ -82,6 +84,14 @@ struct PostView: View {
.allowsHitTesting(false)
}
}
+
+ // This if-block observes @ for tagging
+ if let searching = get_searching_string(post) {
+ VStack {
+ Spacer()
+ UserSearch(damus_state: damus_state, search: searching, post: $post)
+ }.zIndex(1)
+ }
}
.onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
@@ -92,3 +102,23 @@ struct PostView: View {
}
}
+func get_searching_string(_ post: String) -> String? {
+ guard let last_word = post.components(separatedBy: .whitespacesAndNewlines).last else {
+ return nil
+ }
+
+ guard last_word.count >= 2 else {
+ return nil
+ }
+
+ guard last_word.first! == "@" else {
+ return nil
+ }
+
+ // don't include @npub... strings
+ guard last_word.count != 64 else {
+ return nil
+ }
+
+ return String(last_word.dropFirst())
+}
diff --git a/damus/Views/Posting/UserSearch.swift b/damus/Views/Posting/UserSearch.swift
@@ -0,0 +1,87 @@
+//
+// UserAutocompletion.swift
+// damus
+//
+// Created by William Casarin on 2023-01-28.
+//
+
+import SwiftUI
+
+struct SearchedUser: Identifiable {
+ let petname: String?
+ let profile: Profile?
+ let pubkey: String
+
+ var id: String {
+ return pubkey
+ }
+}
+
+struct UserSearch: View {
+ let damus_state: DamusState
+ let search: String
+ @Binding var post: String
+
+ var users: [SearchedUser] {
+ guard let contacts = damus_state.contacts.event else {
+ return []
+ }
+
+ return search_users(profiles: damus_state.profiles, tags: contacts.tags, search: search)
+ }
+
+ var body: some View {
+ ScrollView {
+ LazyVStack {
+ ForEach(users) { user in
+ UserView(damus_state: damus_state, pubkey: user.pubkey)
+ .onTapGesture {
+ guard let pk = bech32_pubkey(user.pubkey) else {
+ return
+ }
+ post = post.replacingOccurrences(of: "@"+search, with: "@"+pk)
+ }
+ }
+ }
+ }
+ }
+}
+
+struct UserSearch_Previews: PreviewProvider {
+ static let search: String = "jb55"
+ @State static var post: String = "some @jb55"
+
+ static var previews: some View {
+ UserSearch(damus_state: test_damus_state(), search: search, post: $post)
+ }
+}
+
+
+func search_users(profiles: Profiles, tags: [[String]], search: String) -> [SearchedUser] {
+ var seen_user = Set<String>()
+ return tags.reduce(into: Array<SearchedUser>()) { arr, tag in
+ guard tag.count >= 2 && tag[0] == "p" else {
+ return
+ }
+
+ let pubkey = tag[1]
+ guard !seen_user.contains(pubkey) else {
+ return
+ }
+ seen_user.insert(pubkey)
+
+ var petname: String? = nil
+ if tag.count >= 4 {
+ petname = tag[3]
+ }
+
+ let profile = profiles.lookup(id: pubkey)
+
+ guard ((petname?.hasPrefix(search) ?? false) || (profile?.name?.hasPrefix(search) ?? false)) else {
+ return
+ }
+
+ let searched_user = SearchedUser(petname: petname, profile: profile, pubkey: pubkey)
+ arr.append(searched_user)
+ }
+}
diff --git a/damus/Views/ReplyView.swift b/damus/Views/ReplyView.swift
@@ -47,7 +47,7 @@ struct ReplyView: View {
ScrollView {
EventView(damus: damus, event: replying_to, has_action_bar: false)
}
- PostView(replying_to: replying_to, references: references)
+ PostView(replying_to: replying_to, references: gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to), damus_state: damus)
}
.onAppear {
references = gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to)