Browse Source

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
translations_damus-localizations-en-us-xcloc-localized-contents-en-us-xliff--master_pl_PL
Swift 2 years ago
committed by William Casarin
parent
commit
b6b6d033a8
  1. 12
      damus.xcodeproj/project.pbxproj
  2. 2
      damus/ContentView.swift
  3. 32
      damus/Views/PostView.swift
  4. 87
      damus/Views/Posting/UserSearch.swift
  5. 2
      damus/Views/ReplyView.swift

12
damus.xcodeproj/project.pbxproj

@ -171,6 +171,7 @@
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */; }; 4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */; };
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; }; 4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; };
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.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 */; }; 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; }; 5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
@ -587,6 +589,7 @@
4C75EFA227FA576C0006080F /* Views */ = { 4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4CF0ABF42985CD4200D66079 /* Posting */,
4CF0ABDF2981A83000D66079 /* Muting */, 4CF0ABDF2981A83000D66079 /* Muting */,
4CC7AAEE297F11B300430951 /* Events */, 4CC7AAEE297F11B300430951 /* Events */,
3AA24800297E3DAE0090C62D /* Reposts */, 3AA24800297E3DAE0090C62D /* Reposts */,
@ -846,6 +849,14 @@
path = AnyCodable; path = AnyCodable;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4CF0ABF42985CD4200D66079 /* Posting */ = {
isa = PBXGroup;
children = (
4CF0ABF52985CD5500D66079 /* UserSearch.swift */,
);
path = Posting;
sourceTree = "<group>";
};
F7F0BA23297892AE009531F3 /* Modifiers */ = { F7F0BA23297892AE009531F3 /* Modifiers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1014,6 +1025,7 @@
4C216F34286F5ACD00040376 /* DMView.swift in Sources */, 4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
4C3EA64428FF558100C48A62 /* sha256.c in Sources */, 4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */, 4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */,
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
4C363AA828297703006E126D /* InsertSort.swift in Sources */, 4C363AA828297703006E126D /* InsertSort.swift in Sources */,
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */, 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */,
4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */, 4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */,

2
damus/ContentView.swift

@ -302,7 +302,7 @@ struct ContentView: View {
case .report(let target): case .report(let target):
MaybeReportView(target: target) MaybeReportView(target: target)
case .post: case .post:
PostView(replying_to: nil, references: []) PostView(replying_to: nil, references: [], damus_state: damus_state!)
case .reply(let event): case .reply(let event):
ReplyView(replying_to: event, damus: damus_state!) ReplyView(replying_to: event, damus: damus_state!)
} }

32
damus/Views/PostView.swift

@ -16,10 +16,11 @@ let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Tex
struct PostView: View { struct PostView: View {
@State var post: String = "" @State var post: String = ""
@FocusState var focus: Bool
let replying_to: NostrEvent? let replying_to: NostrEvent?
@FocusState var focus: Bool
let references: [ReferencedId] let references: [ReferencedId]
let damus_state: DamusState
@Environment(\.presentationMode) var presentationMode @Environment(\.presentationMode) var presentationMode
@ -74,6 +75,7 @@ struct PostView: View {
TextEditor(text: $post) TextEditor(text: $post)
.focused($focus) .focused($focus)
.textInputAutocapitalization(.sentences) .textInputAutocapitalization(.sentences)
if post.isEmpty { if post.isEmpty {
Text(POST_PLACEHOLDER) Text(POST_PLACEHOLDER)
.padding(.top, 8) .padding(.top, 8)
@ -82,6 +84,14 @@ struct PostView: View {
.allowsHitTesting(false) .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() { .onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 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())
}

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

2
damus/Views/ReplyView.swift

@ -47,7 +47,7 @@ struct ReplyView: View {
ScrollView { ScrollView {
EventView(damus: damus, event: replying_to, has_action_bar: false) 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 { .onAppear {
references = gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to) references = gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to)

Loading…
Cancel
Save