ndb: switch to nostrdb notes

This is a refactor of the codebase to use a more memory-efficient
representation of notes. It should also be much faster at decoding since
we're using a custom C json parser now.

Changelog-Changed: Improved memory usage and performance when processing events
This commit is contained in:
William Casarin
2023-07-26 08:46:44 -07:00
parent 55bbe8f855
commit cebd1f48ca
110 changed files with 2153 additions and 1799 deletions

View File

@@ -106,11 +106,7 @@ struct EventActionBar: View {
}
}
.sheet(isPresented: $show_share_sheet, onDismiss: { self.show_share_sheet = false }) {
if let note_id = bech32_note_id(event.id) {
if let url = URL(string: "https://damus.io/" + note_id) {
ShareSheet(activityItems: [url])
}
}
ShareSheet(activityItems: [URL(string: "https://damus.io/" + event.id.bech32)!])
}
.sheet(isPresented: $show_repost_action, onDismiss: { self.show_repost_action = false }) {

View File

@@ -38,7 +38,7 @@ struct ShareAction: View {
ShareActionButton(img: "link", text: NSLocalizedString("Copy Link", comment: "Button to copy link to note")) {
dismiss()
UIPasteboard.general.string = "https://damus.io/" + (bech32_note_id(event.id) ?? event.id)
UIPasteboard.general.string = "https://damus.io/" + event.id.bech32
}
let bookmarkImg = isBookmarked ? "bookmark.fill" : "bookmark"

View File

@@ -36,18 +36,18 @@ struct GradientFollowButton: View {
)
}
.onReceive(handle_notify(.followed)) { ref in
guard target.pubkey == ref.ref_id else { return }
guard target.follow_ref == ref else { return }
self.follow_state = .follows
}
.onReceive(handle_notify(.unfollowed)) { ref in
guard target.pubkey == ref.ref_id else { return }
guard target.follow_ref == ref else { return }
self.follow_state = .unfollows
}
}
}
struct GradientFollowButtonPreviews: View {
let target: FollowTarget = .pubkey("")
let target: FollowTarget = .pubkey(.empty)
var body: some View {
VStack {
Text(verbatim: "Unfollows")

View File

@@ -135,8 +135,7 @@ struct CreateAccountView_Previews: PreviewProvider {
}
func KeyText(_ pubkey: Binding<Pubkey>) -> some View {
let decoded = hex_decode(pubkey.wrappedValue)!
let bechkey = bech32_encode(hrp: PUBKEY_HRP, decoded)
let bechkey = bech32_encode(hrp: PUBKEY_HRP, pubkey.wrappedValue.bytes)
return Text(bechkey)
.textSelection(.enabled)
.multilineTextAlignment(.center)

View File

@@ -18,7 +18,7 @@ struct DMView: View {
var Mention: some View {
Group {
if let mention = first_eref_mention(ev: event, privkey: damus_state.keypair.privkey) {
BuilderEventView(damus: damus_state, event_id: mention.ref.id)
BuilderEventView(damus: damus_state, event_id: mention.ref)
} else {
EmptyView()
}

View File

@@ -72,10 +72,10 @@ func should_show_images(settings: UserSettingsStore, contacts: Contacts, ev: Nos
}
extension View {
func pubkey_context_menu(bech32_pubkey: Pubkey) -> some View {
func pubkey_context_menu(pubkey: Pubkey) -> some View {
return self.contextMenu {
Button {
UIPasteboard.general.string = bech32_pubkey
UIPasteboard.general.string = pubkey.npub
} label: {
Label(NSLocalizedString("Copy Account ID", comment: "Context menu option for copying the ID of the account that created the note."), image: "copy2")
}

View File

@@ -22,7 +22,7 @@ struct EventTop: View {
func ProfileName(is_anon: Bool) -> some View {
let profile = state.profiles.lookup(id: self.pubkey)
let pk = is_anon ? "anon" : self.pubkey
let pk = is_anon ? ANON_PUBKEY : self.pubkey
return EventProfileName(pubkey: pk, profile: profile, damus: state, size: .normal)
}

View File

@@ -37,9 +37,9 @@ func reply_desc(profiles: Profiles, event: NostrEvent, locale: Locale = Locale.c
return NSLocalizedString("Replying to self", bundle: bundle, comment: "Label to indicate that the user is replying to themself.")
}
let names: [String] = pubkeys.map {
let prof = profiles.lookup(id: $0)
return Profile.displayName(profile: prof, pubkey: $0).username.truncate(maxLength: 50)
let names: [String] = pubkeys.map { pk in
let prof = profiles.lookup(id: pk)
return Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
}
let uniqueNames = NSOrderedSet(array: names).array as! [String]

View File

@@ -27,9 +27,7 @@ struct EventMenuContext: View {
var body: some View {
HStack {
Menu {
MenuItems(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks, muted_threads: muted_threads, settings: settings)
} label: {
Label("", systemImage: "ellipsis")
.foregroundColor(Color.gray)
@@ -77,13 +75,13 @@ struct MenuItems: View {
}
Button {
UIPasteboard.general.string = bech32_pubkey(target_pubkey)
UIPasteboard.general.string = target_pubkey.npub
} label: {
Label(NSLocalizedString("Copy user public key", comment: "Context menu option for copying the ID of the user who created the note."), image: "user")
}
Button {
UIPasteboard.general.string = bech32_note_id(event.id) ?? event.id
UIPasteboard.general.string = event.id.bech32
} label: {
Label(NSLocalizedString("Copy note ID", comment: "Context menu option for copying the ID of the note."), image: "note-book")
}

View File

@@ -34,7 +34,7 @@ struct EventShell<Content: View>: View {
!options.contains(.no_action_bar)
}
func get_mention() -> Mention? {
func get_mention() -> Mention<NoteId>? {
if self.options.contains(.nested) || self.options.contains(.no_mentions) {
return nil
}
@@ -42,8 +42,8 @@ struct EventShell<Content: View>: View {
return first_eref_mention(ev: event, privkey: state.keypair.privkey)
}
func Mention(_ mention: Mention) -> some View {
return BuilderEventView(damus: state, event_id: mention.ref.id)
func Mention(_ mention: Mention<NoteId>) -> some View {
return BuilderEventView(damus: state, event_id: mention.ref)
}
var ActionBar: some View {

View File

@@ -20,12 +20,12 @@ struct LongformEvent {
for tag in ev.tags {
guard tag.count >= 2 else { continue }
switch tag[0] {
case "title": longform.title = tag[1]
case "image": longform.image = URL(string: tag[1])
case "summary": longform.summary = tag[1]
switch tag[0].string() {
case "title": longform.title = tag[1].string()
case "image": longform.image = URL(string: tag[1].string())
case "summary": longform.summary = tag[1].string()
case "published_at":
longform.published_at = Double(tag[1]).map { d in Date(timeIntervalSince1970: d) }
longform.published_at = Double(tag[1].string()).map { d in Date(timeIntervalSince1970: d) }
default:
break
}

View File

@@ -50,7 +50,7 @@ struct SelectedEventView: View {
EventBody(damus_state: damus, event: event, size: size, options: [.wide])
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
BuilderEventView(damus: damus, event_id: mention.ref.id)
BuilderEventView(damus: damus, event_id: mention.ref)
.padding(.horizontal)
}

View File

@@ -59,7 +59,7 @@ struct TextEvent: View {
func event_has_tag(ev: NostrEvent, tag: String) -> Bool {
for t in ev.tags {
if t.count >= 1 && t[0] == tag {
if t.count >= 1 && t[0].matches_str(tag) {
return true
}
}

View File

@@ -31,18 +31,16 @@ struct FollowButtonView: View {
.stroke(follow_state == .unfollows ? .clear : borderColor(), lineWidth: 1)
}
}
.onReceive(handle_notify(.followed)) { pk in
guard pk.key == "p", target.pubkey == pk.ref_id else {
return
}
.onReceive(handle_notify(.followed)) { follow in
guard case .pubkey(let pk) = follow,
pk == target.pubkey else { return }
self.follow_state = .follows
}
.onReceive(handle_notify(.unfollowed)) { pk in
guard pk.key == "p", target.pubkey == pk.ref_id else {
return
}
.onReceive(handle_notify(.unfollowed)) { unfollow in
guard case .pubkey(let pk) = unfollow,
pk == target.pubkey else { return }
self.follow_state = .unfollows
}
}
@@ -65,7 +63,7 @@ struct FollowButtonView: View {
}
struct FollowButtonPreviews: View {
let target: FollowTarget = .pubkey("")
let target: FollowTarget = .pubkey(test_pubkey)
var body: some View {
VStack {
Text(verbatim: "Unfollows")

View File

@@ -146,10 +146,8 @@ func parse_key(_ thekey: String) -> ParsedKey? {
if let bech_key = decode_bech32_key(key) {
switch bech_key {
case .pub(let pk):
return .pub(pk)
case .sec(let sec):
return .priv(sec)
case .pub(let pk): return .pub(pk)
case .sec(let sec): return .priv(sec)
}
}
@@ -195,11 +193,12 @@ func process_login(_ key: ParsedKey, is_pubkey: Bool) async throws {
save_pubkey(pubkey: nip05.pubkey)
case .hex(let hexstr):
if is_pubkey {
if is_pubkey, let pubkey = hex_decode_pubkey(hexstr) {
try clear_saved_privkey()
save_pubkey(pubkey: hexstr)
} else {
try handle_privkey(hexstr)
save_pubkey(pubkey: pubkey)
} else if let privkey = hex_decode_privkey(hexstr) {
try handle_privkey(privkey)
}
}
@@ -209,10 +208,8 @@ func process_login(_ key: ParsedKey, is_pubkey: Bool) async throws {
guard let pk = privkey_to_pubkey(privkey: privkey) else {
throw LoginError.invalid_key
}
if let pub = bech32_pubkey(pk), let priv = bech32_privkey(privkey) {
CredentialHandler().save_credential(pubkey: pub, privkey: priv)
}
CredentialHandler().save_credential(pubkey: pk, privkey: privkey)
save_pubkey(pubkey: pk)
}
@@ -232,7 +229,7 @@ struct NIP05Result: Decodable {
struct NIP05User {
let pubkey: Pubkey
let relays: [String]
//let relays: [String]
}
func get_nip05_pubkey(id: String) async -> NIP05User? {
@@ -245,30 +242,24 @@ func get_nip05_pubkey(id: String) async -> NIP05User? {
let user = parts[0]
let host = parts[1]
guard let url = URL(string: "https://\(host)/.well-known/nostr.json?name=\(user)") else {
return nil
}
guard let (data, _) = try? await URLSession.shared.data(for: URLRequest(url: url)) else {
return nil
}
guard let json: NIP05Result = decode_data(data) else {
return nil
}
guard let pubkey = json.names[user] else {
guard let url = URL(string: "https://\(host)/.well-known/nostr.json?name=\(user)"),
let (data, _) = try? await URLSession.shared.data(for: URLRequest(url: url)),
let json: NIP05Result = decode_data(data),
let pubkey_hex = json.names[user],
let pubkey = hex_decode_pubkey(pubkey_hex)
else {
return nil
}
/*
var relays: [String] = []
if let rs = json.relays {
if let rs = rs[pubkey] {
relays = rs
}
}
return NIP05User(pubkey: pubkey, relays: relays)
if let rs = json.relays, let rs = rs[pubkey] {
relays = rs
}
*/
return NIP05User(pubkey: pubkey/*, relays: relays*/)
}
struct KeyInput: View {

View File

@@ -13,15 +13,12 @@ struct MutelistView: View {
func RemoveAction(pubkey: Pubkey) -> some View {
Button {
guard let mutelist = damus_state.contacts.mutelist else {
return
}
guard let keypair = damus_state.keypair.to_full() else {
return
}
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: pubkey) else {
guard let mutelist = damus_state.contacts.mutelist,
let keypair = damus_state.keypair.to_full(),
let new_ev = remove_from_mutelist(keypair: keypair,
prev: mutelist,
to_remove: .pubkey(pubkey))
else {
return
}
@@ -48,22 +45,15 @@ struct MutelistView: View {
}
.navigationTitle(NSLocalizedString("Muted Users", comment: "Navigation title of view to see list of muted users."))
.onAppear {
users = get_mutelist_users(damus_state.contacts.mutelist)
users = get_mutelist_users(damus_state.contacts.mutelist)
}
}
}
func get_mutelist_users(_ mlist: NostrEvent?) -> [String] {
guard let mutelist = mlist else {
return []
}
return mutelist.tags.reduce(into: Array<String>()) { pks, tag in
if tag.count >= 2 && tag[0] == "p" {
pks.append(tag[1])
}
}
func get_mutelist_users(_ mutelist: NostrEvent?) -> Array<Pubkey> {
guard let mutelist else { return [] }
return Array(mutelist.referenced_pubkeys)
}
struct MutelistView_Previews: PreviewProvider {

View File

@@ -230,7 +230,7 @@ struct NoteContentView: View {
for block in blocks.blocks {
switch block {
case .mention(let m):
if m.type == .pubkey && m.ref.ref_id == profile.pubkey {
if case .pubkey(let pk) = m.ref, pk == profile.pubkey {
load(force_artifacts: true)
return
}
@@ -265,21 +265,21 @@ func url_str(_ url: URL) -> CompatibleText {
return CompatibleText(attributed: attributedString)
}
func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText {
switch m.type {
case .pubkey:
let pk = m.ref.ref_id
func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText {
switch m.ref {
case .pubkey(let pk):
let npub = bech32_pubkey(pk)
let profile = profiles.lookup(id: pk)
let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
var attributedString = AttributedString(stringLiteral: "@\(disp)")
attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))")
attributedString.link = URL(string: "damus:nostr:\(npub)")
attributedString.foregroundColor = DamusColors.purple
return CompatibleText(attributed: attributedString)
case .event:
let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id
case .note(let note_id):
let bevid = bech32_note_id(note_id)
var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))")
attributedString.link = URL(string: "damus:\(encode_event_id_uri(m.ref))")
attributedString.link = URL(string: "damus:nostr:\(bevid)")
attributedString.foregroundColor = DamusColors.purple
return CompatibleText(attributed: attributedString)
@@ -394,7 +394,7 @@ func note_artifact_is_separated(kind: NostrKind?) -> Bool {
func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: Privkey?) -> NoteArtifacts {
let blocks = ev.blocks(privkey)
if ev.known_kind == .longform {
return .longform(LongformContent(ev.content))
}
@@ -427,7 +427,9 @@ func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_note_ref: Boo
if let next = blocks[safe: ind+1] {
if case .url(let u) = next, classify_url(u).is_media != nil {
trimmed = trim_suffix(trimmed)
} else if case .mention(let m) = next, m.type == .event, one_note_ref {
} else if case .mention(let m) = next,
case .note = m.ref,
one_note_ref {
trimmed = trim_suffix(trimmed)
}
}
@@ -450,7 +452,7 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSepara
switch block {
case .mention(let m):
if m.type == .event && one_note_ref {
if case .note = m.ref, one_note_ref {
return str
}
return str + mention_str(m, profiles: profiles)

View File

@@ -10,10 +10,10 @@ import SwiftUI
struct ParticipantsView: View {
let damus_state: DamusState
@Binding var references: [ReferencedId]
@Binding var originalReferences: [ReferencedId]
let original_pubkeys: [Pubkey]
@Binding var filtered_pubkeys: Set<Pubkey>
var body: some View {
VStack {
Text("Replying to", comment: "Text indicating that the view is used for editing which participants are replied to in a note.")
@@ -23,7 +23,7 @@ struct ParticipantsView: View {
Button {
// Remove all "p" refs, keep "e" refs
references = originalReferences.eRefs
filtered_pubkeys = Set(original_pubkeys)
} label: {
Text("Remove all", comment: "Button label to remove all participants from a note reply.")
}
@@ -34,7 +34,7 @@ struct ParticipantsView: View {
.clipShape(Capsule())
Button {
references = originalReferences
filtered_pubkeys = []
} label: {
Text("Add all", comment: "Button label to re-add all original participants as profiles to reply to in a note")
}
@@ -48,26 +48,19 @@ struct ParticipantsView: View {
}
VStack {
ScrollView {
ForEach(originalReferences.pRefs) { participant in
let pubkey = participant.id
ForEach(original_pubkeys) { pubkey in
HStack {
UserView(damus_state: damus_state, pubkey: pubkey)
Image("check-circle.fill")
.font(.system(size: 30))
.foregroundColor(references.contains(participant) ? DamusColors.purple : .gray)
.foregroundColor(filtered_pubkeys.contains(pubkey) ? .gray : DamusColors.purple)
}
.onTapGesture {
if references.contains(participant) {
references = references.filter {
$0 != participant
}
if filtered_pubkeys.contains(pubkey) {
filtered_pubkeys.remove(pubkey)
} else {
if references.contains(participant) {
// Don't add it twice
} else {
references.append(participant)
}
filtered_pubkeys.insert(pubkey)
}
}
}

View File

@@ -50,8 +50,8 @@ struct PostView: View {
@State var error: String? = nil
@State var uploadedMedias: [UploadedMedia] = []
@State var image_upload_confirm: Bool = false
@State var originalReferences: [ReferencedId] = []
@State var references: [ReferencedId] = []
@State var references: [RefId] = []
@State var filtered_pubkeys: Set<Pubkey> = []
@State var focusWordAttributes: (String?, NSRange?) = (nil, nil)
@State var newCursorIndex: Int?
@State var postTextViewCanScroll: Bool = true
@@ -76,7 +76,13 @@ struct PostView: View {
}
func send_post() {
let new_post = build_post(post: self.post, action: action, uploadedMedias: uploadedMedias, references: references)
let refs = references.filter { ref in
if case .pubkey(let pk) = ref, filtered_pubkeys.contains(pk) {
return false
}
return true
}
let new_post = build_post(post: self.post, action: action, uploadedMedias: uploadedMedias, references: refs)
notify(.post(.post(new_post)))
@@ -155,8 +161,7 @@ struct PostView: View {
}
let profile = damus_state.profiles.lookup(id: pubkey)
let bech32_pubkey = bech32_pubkey(pubkey) ?? ""
return user_tag_attr_string(profile: profile, pubkey: bech32_pubkey)
return user_tag_attr_string(profile: profile, pubkey: pubkey)
}
func clear_draft() {
@@ -310,7 +315,17 @@ struct PostView: View {
self.post = initialString()
self.tagModel.diff = post.string.count
}
var pubkeys: [Pubkey] {
self.references.reduce(into: [Pubkey]()) { pks, ref in
guard case .pubkey(let pk) = ref else {
return
}
pks.append(pk)
}
}
var body: some View {
GeometryReader { (deviceSize: GeometryProxy) in
VStack(alignment: .leading, spacing: 0) {
@@ -321,7 +336,7 @@ struct PostView: View {
ScrollViewReader { scroller in
ScrollView {
if case .replying_to(let replying_to) = self.action {
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
ReplyView(replying_to: replying_to, damus: damus_state, original_pubkeys: pubkeys, filtered_pubkeys: $filtered_pubkeys)
}
Editor(deviceSize: deviceSize)
@@ -385,10 +400,8 @@ struct PostView: View {
switch action {
case .replying_to(let replying_to):
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
originalReferences = references
case .quoting(let quoting):
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
originalReferences = references
case .posting(let target):
guard !loaded_draft else { break }
@@ -551,13 +564,7 @@ func load_draft_for_post(drafts: Drafts, action: PostAction) -> DraftArtifacts?
}
func build_post(post: NSMutableAttributedString, action: PostAction, uploadedMedias: [UploadedMedia], references: [ReferencedId]) -> NostrPost {
var kind: NostrKind = .text
if case .replying_to(let ev) = action, ev.known_kind == .chat {
kind = .chat
}
func build_post(post: NSMutableAttributedString, action: PostAction, uploadedMedias: [UploadedMedia], references: [RefId]) -> NostrPost {
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
if let link = attributes[.link] as? String {
let normalized_link: String
@@ -586,9 +593,9 @@ func build_post(post: NSMutableAttributedString, action: PostAction, uploadedMed
content.append(" " + imagesString + " ")
}
if case .quoting(let ev) = action, let id = bech32_note_id(ev.id) {
content.append(" nostr:" + id)
if case .quoting(let ev) = action {
content.append(" nostr:" + bech32_note_id(ev.id))
}
return NostrPost(content: content, references: references, kind: kind, tags: img_meta_tags)
return NostrPost(content: content, references: references, kind: .text, tags: img_meta_tags)
}

View File

@@ -31,10 +31,7 @@ struct UserSearch: View {
}
func on_user_tapped(user: SearchedUser) {
guard let pk = bech32_pubkey(user.pubkey) else {
return
}
let pk = user.pubkey
let user_tag = user_tag_attr_string(profile: user.profile, pubkey: pk)
appendUserTag(withTag: user_tag)
@@ -159,7 +156,7 @@ func user_tag_attr_string(profile: Profile?, pubkey: Pubkey) -> NSMutableAttribu
return NSMutableAttributedString(string: tagString, attributes: [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
NSAttributedString.Key.foregroundColor: UIColor.label,
NSAttributedString.Key.link: "damus:nostr:\(pubkey)"
NSAttributedString.Key.link: "damus:nostr:\(pubkey.npub)"
])
}

View File

@@ -39,7 +39,7 @@ struct AboutView: View {
}
}
.onAppear {
let blocks = parse_note_content(content: about, tags: [])
let blocks = parse_note_content(content: .content(about, nil))
about_string = render_blocks(blocks: blocks, profiles: state.profiles).content.attributed
}

View File

@@ -45,7 +45,7 @@ struct ProfileNameView: View {
Spacer()
KeyView(pubkey: pubkey)
.pubkey_context_menu(bech32_pubkey: pubkey)
.pubkey_context_menu(pubkey: pubkey)
}
}
}

View File

@@ -42,7 +42,8 @@ struct EditProfilePictureView: View {
private func get_profile_url() -> URL? {
if let profile_url {
return profile_url
} else if let state = damus_state, let picture = state.profiles.lookup(id: pubkey)?.picture {
} else if let state = damus_state,
let picture = state.profiles.lookup(id: pubkey)?.picture {
return URL(string: picture)
} else {
return profile_url ?? URL(string: robohash(pubkey))

View File

@@ -190,7 +190,7 @@ struct ProfileView: View {
return
}
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: profile.pubkey) else {
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: .pubkey(profile.pubkey)) else {
return
}
@@ -260,10 +260,11 @@ struct ProfileView: View {
func actionSection(profile_data: Profile?) -> some View {
return Group {
if let profile = profile_data {
if let lnurl = profile.lnurl, lnurl != "" {
lnButton(lnurl: lnurl, profile: profile)
}
if let profile = profile_data,
let lnurl = profile.lnurl,
lnurl != ""
{
lnButton(lnurl: lnurl, profile: profile)
}
dmButton
@@ -353,7 +354,7 @@ struct ProfileView: View {
HStack {
if let contact = profile.contacts {
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
let contacts = Array(contact.referenced_pubkeys)
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
NavigationLink(value: Route.Following(following: following_model)) {
HStack {
@@ -466,11 +467,8 @@ struct ProfileView: View {
// our profilemodel needs a bit more help
}
.sheet(isPresented: $show_share_sheet) {
if let npub = bech32_pubkey(profile.pubkey) {
if let url = URL(string: "https://damus.io/" + npub) {
ShareSheet(activityItems: [url])
}
}
let url = URL(string: "https://damus.io/" + profile.pubkey.npub)!
ShareSheet(activityItems: [url])
}
.fullScreenCover(isPresented: $show_qr_code) {
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
@@ -517,7 +515,7 @@ struct KeyView: View {
}
var body: some View {
let bech32 = bech32_pubkey(pubkey) ?? pubkey
let bech32 = pubkey.npub
HStack {
Text(verbatim: "\(abbrev_pubkey(bech32, amount: 16))")

View File

@@ -10,9 +10,13 @@ import CoreImage.CIFilterBuiltins
struct ProfileScanResult: Equatable {
let pubkey: Pubkey
init(hex: String) {
self.pubkey = hex
init?(hex: String) {
guard let pk = hex_decode(hex).map({ bytes in Pubkey(Data(bytes)) }) else {
return nil
}
self.pubkey = pk
}
init?(string: String) {
@@ -25,14 +29,17 @@ struct ProfileScanResult: Equatable {
str.removeFirst("nostr:".count)
}
if let _ = hex_decode(str), str.count == 64 {
self = .init(hex: str)
if let decoded = hex_decode(str),
str.count == 64
{
self.pubkey = Pubkey(Data(decoded))
return
}
if str.starts(with: "npub"), let b32 = try? bech32_decode(str) {
let hex = hex_encode(b32.data)
self = .init(hex: hex)
if str.starts(with: "npub"),
let b32 = try? bech32_decode(str)
{
self.pubkey = Pubkey(b32.data)
return
}
@@ -56,14 +63,6 @@ struct QRCodeView: View {
let generator = UIImpactFeedbackGenerator(style: .light)
var maybe_key: String? {
guard let key = bech32_pubkey(pubkey) else {
return nil
}
return key
}
@ViewBuilder
func navImage(systemImage: String) -> some View {
Image(systemName: systemImage)
@@ -143,18 +142,16 @@ struct QRCodeView: View {
Spacer()
if let key = maybe_key {
Image(uiImage: generateQRCode(pubkey: "nostr:" + key))
.interpolation(.none)
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
.cornerRadius(10)
.overlay(RoundedRectangle(cornerRadius: 10)
.stroke(DamusColors.white, lineWidth: 5.0))
.shadow(radius: 10)
}
Image(uiImage: generateQRCode(pubkey: "nostr:" + pubkey.npub))
.interpolation(.none)
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
.cornerRadius(10)
.overlay(RoundedRectangle(cornerRadius: 10)
.stroke(DamusColors.white, lineWidth: 5.0))
.shadow(radius: 10)
Spacer()
Text("Follow me on Nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")

View File

@@ -91,6 +91,7 @@ struct RelayDetailView: View {
}
}
}
if let relay_connection {
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
HStack {
@@ -101,7 +102,7 @@ struct RelayDetailView: View {
}
}
if let nip11 = nip11 {
if let nip11 {
if nip11.is_paid {
Section(content: {
RelayPaidDetail(payments_url: nip11.payments_url)
@@ -172,7 +173,7 @@ struct RelayDetailView: View {
struct RelayDetailView_Previews: PreviewProvider {
static var previews: some View {
let metadata = RelayMetadata(name: "name", description: "desc", pubkey: "pubkey", contact: "contact", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com")
let metadata = RelayMetadata(name: "name", description: "desc", pubkey: test_pubkey, contact: "contact", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com")
RelayDetailView(state: test_damus_state(), relay: "relay", nip11: metadata)
}
}

View File

@@ -93,7 +93,7 @@ struct RelayView: View {
}
}
func RemoveButton(privkey: String, showText: Bool) -> some View {
func RemoveButton(privkey: Privkey, showText: Bool) -> some View {
Button(action: {
guard let ev = state.contacts.event else {
return

View File

@@ -10,17 +10,23 @@ import SwiftUI
struct ReplyView: View {
let replying_to: NostrEvent
let damus: DamusState
@Binding var originalReferences: [ReferencedId]
@Binding var references: [ReferencedId]
let original_pubkeys: [Pubkey]
@Binding var filtered_pubkeys: Set<Pubkey>
@State var participantsShown: Bool = false
var references: [Pubkey] {
original_pubkeys.filter { pk in
!filtered_pubkeys.contains(pk)
}
}
var ReplyingToSection: some View {
HStack {
Group {
let names = references.pRefs
let names = references
.map { pubkey in
let pk = pubkey.ref_id
let pk = pubkey
let prof = damus.profiles.lookup(id: pk)
return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
}
@@ -40,11 +46,15 @@ struct ReplyView: View {
}
.sheet(isPresented: $participantsShown) {
if #available(iOS 16.0, *) {
ParticipantsView(damus_state: damus, references: $references, originalReferences: $originalReferences)
ParticipantsView(damus_state: damus,
original_pubkeys: self.original_pubkeys,
filtered_pubkeys: $filtered_pubkeys)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
} else {
ParticipantsView(damus_state: damus, references: $references, originalReferences: $originalReferences)
ParticipantsView(damus_state: damus,
original_pubkeys: self.original_pubkeys,
filtered_pubkeys: $filtered_pubkeys)
}
}
.padding(.leading, 75)
@@ -81,10 +91,16 @@ struct ReplyView: View {
struct ReplyView_Previews: PreviewProvider {
static var previews: some View {
VStack {
ReplyView(replying_to: test_note, damus: test_damus_state(), originalReferences: .constant([]), references: .constant([]))
ReplyView(replying_to: test_note,
damus: test_damus_state(),
original_pubkeys: [],
filtered_pubkeys: .constant([]))
.frame(height: 300)
ReplyView(replying_to: test_longform_event.event, damus: test_damus_state(), originalReferences: .constant([]), references: .constant([]))
ReplyView(replying_to: test_longform_event.event,
damus: test_damus_state(),
original_pubkeys: [],
filtered_pubkeys: .constant([]))
.frame(height: 300)
}
}

View File

@@ -51,12 +51,8 @@ struct ReportView: View {
return
}
guard let note_id = bech32_note_id(ev.id) else {
return
}
report_sent = true
report_id = note_id
report_id = bech32_note_id(ev.id)
}
var send_report_button_text: String {
@@ -131,9 +127,9 @@ struct ReportView_Previews: PreviewProvider {
let ds = test_damus_state()
VStack {
ReportView(postbox: ds.postbox, target: ReportTarget.user(""), keypair: test_keypair.to_full()!)
ReportView(postbox: ds.postbox, target: ReportTarget.user(test_pubkey), keypair: test_keypair.to_full()!)
ReportView(postbox: ds.postbox, target: ReportTarget.user(""), keypair: test_keypair.to_full()!, report_sent: true, report_id: "report_id")
ReportView(postbox: ds.postbox, target: ReportTarget.user(test_pubkey), keypair: test_keypair.to_full()!, report_sent: true, report_id: "report_id")
}
}

View File

@@ -38,7 +38,7 @@ struct SaveKeysView: View {
Text("This is your account ID, you can give this to your friends so that they can follow you. Tap to copy.", comment: "Label to describe that a public key is the user's account ID and what they can do with it.")
.padding(.bottom, 10)
SaveKeyView(text: account.pubkey_bech32, textContentType: .username, is_copied: $pub_copied, focus: $pubkey_focused)
SaveKeyView(text: account.pubkey.npub, textContentType: .username, is_copied: $pub_copied, focus: $pubkey_focused)
.padding(.bottom, 10)
if pub_copied {
@@ -49,7 +49,7 @@ struct SaveKeysView: View {
Text("This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!", comment: "Label to describe that a private key is the user's secret account key and what they should do with it.")
.padding(.bottom, 10)
SaveKeyView(text: account.privkey_bech32, textContentType: .newPassword, is_copied: $priv_copied, focus: $privkey_focused)
SaveKeyView(text: account.privkey.nsec, textContentType: .newPassword, is_copied: $priv_copied, focus: $privkey_focused)
.padding(.bottom, 10)
}
@@ -115,8 +115,8 @@ struct SaveKeysView: View {
self.pool.register_handler(sub_id: "signup", handler: handle_event)
credential_handler.save_credential(pubkey: account.pubkey_bech32, privkey: account.privkey_bech32)
credential_handler.save_credential(pubkey: account.pubkey, privkey: account.privkey)
self.loading = true
self.pool.connect()

View File

@@ -14,15 +14,14 @@ enum SearchState {
case not_found
}
enum SearchType {
case event
case profile
case nip05
enum SearchType: Equatable {
case event(NoteId)
case profile(Pubkey)
case nip05(String)
}
struct SearchingEventView: View {
let state: DamusState
let evid: String
let search_type: SearchType
@State var search_state: SearchState = .searching
@@ -38,18 +37,18 @@ struct SearchingEventView: View {
}
}
func handle_search(_ evid: String) {
func handle_search(search: SearchType) {
self.search_state = .searching
switch search_type {
case .nip05:
if let pk = state.profiles.nip05_pubkey[evid] {
switch search {
case .nip05(let nip05):
if let pk = state.profiles.nip05_pubkey[nip05] {
if state.profiles.lookup(id: pk) != nil {
self.search_state = .found_profile(pk)
}
} else {
Task {
guard let nip05 = NIP05.parse(evid) else {
guard let nip05 = NIP05.parse(nip05) else {
self.search_state = .not_found
return
}
@@ -71,16 +70,16 @@ struct SearchingEventView: View {
}
}
case .event:
find_event(state: state, query: .event(evid: evid)) { res in
case .event(let note_id):
find_event(state: state, query: .event(evid: note_id)) { res in
guard case .event(let ev) = res else {
self.search_state = .not_found
return
}
self.search_state = .found(ev)
}
case .profile:
find_event(state: state, query: .profile(pubkey: evid)) { res in
case .profile(let pubkey):
find_event(state: state, query: .profile(pubkey: pubkey)) { res in
guard case .profile(_, let ev) = res else {
self.search_state = .not_found
return
@@ -113,11 +112,11 @@ struct SearchingEventView: View {
Text("\(search_name) not found", comment: "When a note or profile is not found when searching for it via its note id")
}
}
.onChange(of: evid, debounceTime: 0.5) { evid in
handle_search(evid)
.onChange(of: search_type, debounceTime: 0.5) { stype in
handle_search(search: stype)
}
.onAppear {
handle_search(evid)
handle_search(search: search_type)
}
}
}
@@ -125,6 +124,6 @@ struct SearchingEventView: View {
struct SearchingEventView_Previews: PreviewProvider {
static var previews: some View {
let state = test_damus_state()
SearchingEventView(state: state, evid: test_note.id, search_type: .event)
SearchingEventView(state: state, search_type: .event(test_note.id))
}
}

View File

@@ -18,7 +18,7 @@ enum Search: Identifiable {
case profile(Pubkey)
case note(NoteId)
case nip05(String)
case hex(String)
case hex(Data)
case multi(MultiSearch)
var id: String {
@@ -67,28 +67,22 @@ struct InnerSearchResults: View {
HashtagSearch(ht)
case .nip05(let addr):
SearchingEventView(state: damus_state, evid: addr, search_type: .nip05)
case .profile(let prof):
let decoded = try? bech32_decode(prof)
let hex = hex_encode(decoded!.data)
SearchingEventView(state: damus_state, evid: hex, search_type: .profile)
SearchingEventView(state: damus_state, search_type: .nip05(addr))
case .profile(let pubkey):
SearchingEventView(state: damus_state, search_type: .profile(pubkey))
case .hex(let h):
//let prof_view = ProfileView(damus_state: damus_state, pubkey: h)
//let ev_view = ThreadView(damus: damus_state, event_id: h)
VStack(spacing: 10) {
SearchingEventView(state: damus_state, evid: h, search_type: .event)
SearchingEventView(state: damus_state, evid: h, search_type: .profile)
SearchingEventView(state: damus_state, search_type: .event(NoteId(h)))
SearchingEventView(state: damus_state, search_type: .profile(Pubkey(h)))
}
case .note(let nid):
let decoded = try? bech32_decode(nid)
let hex = hex_encode(decoded!.data)
SearchingEventView(state: damus_state, evid: hex, search_type: .event)
SearchingEventView(state: damus_state, search_type: .event(nid))
case .multi(let multi):
VStack {
HashtagSearch(multi.hashtag)
@@ -146,20 +140,18 @@ func search_for_string(profiles: Profiles, _ new: String) -> Search? {
return .hashtag(make_hashtagable(new))
}
if hex_decode(new) != nil, new.count == 64 {
if let new = hex_decode_id(new) {
return .hex(new)
}
if new.starts(with: "npub") {
if (try? bech32_decode(new)) != nil {
return .profile(new)
if let decoded = bech32_pubkey_decode(new) {
return .profile(decoded)
}
}
if new.starts(with: "note") {
if (try? bech32_decode(new)) != nil {
return .note(new)
}
if new.starts(with: "note"), let decoded = try? bech32_decode(new) {
return .note(NoteId(decoded.data))
}
let multisearch = MultiSearch(hashtag: make_hashtagable(new), profiles: search_profiles(profiles: profiles, search: new))
@@ -181,13 +173,19 @@ func make_hashtagable(_ str: String) -> String {
func search_profiles(profiles: Profiles, search: String) -> [SearchedUser] {
// Search by hex pubkey.
if search.count == 64 && hex_decode(search) != nil, let profile = profiles.lookup(id: search) {
return [SearchedUser(profile: profile, pubkey: search)]
if let pubkey = hex_decode_pubkey(search),
let profile = profiles.lookup(id: pubkey)
{
return [SearchedUser(profile: profile, pubkey: pubkey)]
}
// Search by npub pubkey.
if search.starts(with: "npub"), let bech32_key = decode_bech32_key(search), case Bech32Key.pub(let hex) = bech32_key, let profile = profiles.lookup(id: hex) {
return [SearchedUser(profile: profile, pubkey: hex)]
if search.starts(with: "npub"),
let bech32_key = decode_bech32_key(search),
case Bech32Key.pub(let pk) = bech32_key,
let profile = profiles.lookup(id: pk)
{
return [SearchedUser(profile: profile, pubkey: pk)]
}
let new = search.lowercased()

View File

@@ -14,7 +14,7 @@ struct ThreadView: View {
@Environment(\.dismiss) var dismiss
var parent_events: [NostrEvent] {
state.events.parent_events(event: thread.event)
state.events.parent_events(event: thread.event, privkey: state.keypair.privkey)
}
var child_events: [NostrEvent] {
@@ -34,7 +34,7 @@ struct ThreadView: View {
selected: false)
.padding(.horizontal)
.onTapGesture {
thread.set_active_event(parent_event)
thread.set_active_event(parent_event, privkey: self.state.keypair.privkey)
scroll_to_event(scroller: reader, id: parent_event.id, delay: 0.1, animate: false)
}
@@ -77,7 +77,7 @@ struct ThreadView: View {
)
.padding(.horizontal)
.onTapGesture {
thread.set_active_event(child_event)
thread.set_active_event(child_event, privkey: state.keypair.privkey)
scroll_to_event(scroller: reader, id: child_event.id, delay: 0.1, animate: false)
}

View File

@@ -192,7 +192,7 @@ struct WalletView: View {
}
}
let test_wallet_connect_url = WalletConnectURL(pubkey: "pk", relay: .init("wss://relay.damus.io")!, keypair: test_damus_state().keypair.to_full()!, lud16: "jb55@sendsats.com")
let test_wallet_connect_url = WalletConnectURL(pubkey: test_pubkey, relay: .init("wss://relay.damus.io")!, keypair: test_damus_state().keypair.to_full()!, lud16: "jb55@sendsats.com")
struct WalletView_Previews: PreviewProvider {
static let tds = test_damus_state()

View File

@@ -16,7 +16,7 @@ struct ZapsView: View {
init(state: DamusState, target: ZapTarget) {
self.state = state
self.model = ZapsModel(state: state, target: target)
self._zaps = ObservedObject(wrappedValue: state.events.get_cache_data(target.id).zaps_model)
self._zaps = ObservedObject(wrappedValue: state.events.get_cache_data(NoteId(target.id)).zaps_model)
}
var body: some View {
@@ -40,6 +40,6 @@ struct ZapsView: View {
struct ZapsView_Previews: PreviewProvider {
static var previews: some View {
ZapsView(state: test_damus_state(), target: .profile("pk"))
ZapsView(state: test_damus_state(), target: .profile(test_pubkey))
}
}