commit 874d15df45e0fd88e772187ea1561e483d5c8d2d
parent 713effdc35e2707cc5e163753493593806d023fb
Author: William Casarin <jb55@jb55.com>
Date:   Mon, 16 May 2022 16:23:34 -0700
following view
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
8 files changed, 168 insertions(+), 37 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -32,6 +32,8 @@
 		4C363AA428296DEE006E126D /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA328296DEE006E126D /* SearchModel.swift */; };
 		4C363AA828297703006E126D /* InsertSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA728297703006E126D /* InsertSort.swift */; };
 		4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79A28306D7B00E1F516 /* Contacts.swift */; };
+		4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79C2833036D00E1F516 /* FollowingView.swift */; };
+		4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC79E2833115300E1F516 /* FollowButtonView.swift */; };
 		4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */; };
 		4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
 		4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */; };
@@ -117,6 +119,8 @@
 		4C363AA328296DEE006E126D /* SearchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModel.swift; sourceTree = "<group>"; };
 		4C363AA728297703006E126D /* InsertSort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertSort.swift; sourceTree = "<group>"; };
 		4C3AC79A28306D7B00E1F516 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = "<group>"; };
+		4C3AC79C2833036D00E1F516 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = "<group>"; };
+		4C3AC79E2833115300E1F516 /* FollowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowButtonView.swift; sourceTree = "<group>"; };
 		4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModel.swift; sourceTree = "<group>"; };
 		4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrKind.swift; sourceTree = "<group>"; };
 		4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBarModel.swift; sourceTree = "<group>"; };
@@ -234,6 +238,8 @@
 				4C363A8B28236B92006E126D /* PubkeyView.swift */,
 				4C363A8D28236FE4006E126D /* NoteContentView.swift */,
 				4C363AA128296A7E006E126D /* SearchView.swift */,
+				4C3AC79C2833036D00E1F516 /* FollowingView.swift */,
+				4C3AC79E2833115300E1F516 /* FollowButtonView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -481,6 +487,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
 				4C363A8A28236B57006E126D /* MentionView.swift in Sources */,
 				4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
 				4C363AA828297703006E126D /* InsertSort.swift in Sources */,
@@ -509,6 +516,7 @@
 				4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
 				4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
 				4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
+				4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
 				4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
 				4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
 				4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -208,8 +208,7 @@ struct ContentView: View {
         Group {
             if let pk = self.active_profile {
                 let profile_model = ProfileModel(pubkey: pk, damus: damus_state!)
-                let fs = damus_state!.contacts.follow_state(pk)
-                ProfileView(damus: damus_state!, follow_state: fs, profile: profile_model)
+                ProfileView(damus_state: damus_state!, profile: profile_model)
             } else {
                 EmptyView()
             }
diff --git a/damus/Models/ProfileModel.swift b/damus/Models/ProfileModel.swift
@@ -9,17 +9,18 @@ import Foundation
 
 class ProfileModel: ObservableObject {
     @Published var events: [NostrEvent] = []
+    @Published var contacts: NostrEvent? = nil
+    @Published var following: Int = 0
+    
     let pubkey: String
     let damus: DamusState
     
-    @Published var following: Bool
     var seen_event: Set<String> = Set()
     var sub_id = UUID().description
     
     init(pubkey: String, damus: DamusState) {
         self.pubkey = pubkey
         self.damus = damus
-        self.following = damus.contacts.is_friend(pubkey)
     }
     
     func unsubscribe() {
@@ -44,13 +45,19 @@ class ProfileModel: ObservableObject {
         damus.pool.subscribe(sub_id: sub_id, filters: [filter], handler: handle_event)
     }
     
+    func handle_profile_contact_event(_ ev: NostrEvent) {
+        self.contacts = ev
+        self.following = count_pubkeys(ev.tags)
+    }
+    
     func add_event(_ ev: NostrEvent) {
         if seen_event.contains(ev.id) {
             return
         }
-        if ev.kind == 1 {
-            self.events.append(ev)
-            self.events = self.events.sorted { $0.created_at > $1.created_at }
+        if ev.known_kind == .text {
+            let _ = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at})
+        } else if ev.known_kind == .contacts {
+            handle_profile_contact_event(ev)
         }
         seen_event.insert(ev.id)
     }
@@ -72,3 +79,15 @@ class ProfileModel: ObservableObject {
         }
     }
 }
+
+
+func count_pubkeys(_ tags: [[String]]) -> Int {
+    var c: Int = 0
+    for tag in tags {
+        if tag.count >= 2 && tag[0] == "p" {
+            c += 1
+        }
+    }
+    
+    return c
+}
diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift
@@ -19,10 +19,14 @@ struct KeyEvent {
     let relay_url: String
 }
 
-struct ReferencedId {
+struct ReferencedId: Identifiable {
     let ref_id: String
     let relay_id: String?
     let key: String
+    
+    var id: String {
+        return ref_id
+    }
 }
 
 struct EventId: Identifiable, CustomStringConvertible {
diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift
@@ -69,8 +69,7 @@ struct EventView: View {
             let profile = damus.profiles.lookup(id: event.pubkey)
             VStack {
                 let pmodel = ProfileModel(pubkey: event.pubkey, damus: damus)
-                let fs = damus.contacts.follow_state(event.pubkey)
-                let pv = ProfileView(damus: damus, follow_state: fs, profile: pmodel)
+                let pv = ProfileView(damus_state: damus, profile: pmodel)
                 
                 NavigationLink(destination: pv) {
                     ProfilePicView(pubkey: event.pubkey, size: PFP_SIZE!, highlight: highlight, image_cache: damus.image_cache, profiles: damus.profiles)
@@ -122,9 +121,15 @@ struct EventView: View {
             }
             
             Button {
+                UIPasteboard.general.string = "@" + event.pubkey
+            } label: {
+                Label("Copy User ID", systemImage: "tag")
+            }
+
+            Button {
                 UIPasteboard.general.string = "&" + event.id
             } label: {
-                Label("Copy ID", systemImage: "tag")
+                Label("Copy Note ID", systemImage: "tag")
             }
             
             Button {
diff --git a/damus/Views/FollowButtonView.swift b/damus/Views/FollowButtonView.swift
@@ -0,0 +1,44 @@
+//
+//  FollowButtonView.swift
+//  damus
+//
+//  Created by William Casarin on 2022-05-16.
+//
+
+import SwiftUI
+
+struct FollowButtonView: View {
+    let pubkey: String
+    @State var follow_state: FollowState
+    
+    var body: some View {
+        Button("\(follow_btn_txt(follow_state))") {
+            follow_state = perform_follow_btn_action(follow_state, target: pubkey)
+        }
+        .buttonStyle(.bordered)
+        .onReceive(handle_notify(.followed)) { notif in
+            let pk = notif.object as! String
+            if pk != pubkey {
+                return
+            }
+            
+            self.follow_state = .follows
+        }
+        .onReceive(handle_notify(.unfollowed)) { notif in
+            let pk = notif.object as! String
+            if pk != pubkey {
+                return
+            }
+            
+            self.follow_state = .unfollows
+        }
+    }
+}
+
+    /*
+struct FollowButtonView_Previews: PreviewProvider {
+    static var previews: some View {
+        FollowButtonView()
+    }
+}
+     */
diff --git a/damus/Views/FollowingView.swift b/damus/Views/FollowingView.swift
@@ -0,0 +1,59 @@
+//
+//  FollowingView.swift
+//  damus
+//
+//  Created by William Casarin on 2022-05-16.
+//
+
+import SwiftUI
+
+struct FollowUserView: View {
+    let pubkey: String
+    let damus_state: DamusState
+    
+    var body: some View {
+        HStack(alignment: .top) {
+            let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
+            let pv = ProfileView(damus_state: damus_state, profile: pmodel)
+            
+            NavigationLink(destination: pv) {
+                ProfilePicView(pubkey: pubkey, size: PFP_SIZE!, highlight: .none, image_cache: damus_state.image_cache, profiles: damus_state.profiles)
+            }
+            
+            VStack(alignment: .leading) {
+                let profile = damus_state.profiles.lookup(id: pubkey)
+                ProfileName(pubkey: pubkey, profile: profile)
+                if let about = profile.flatMap { $0.about } {
+                    Text(about)
+                }
+            }
+            
+            Spacer()
+            
+            FollowButtonView(pubkey: pubkey, follow_state: damus_state.contacts.follow_state(pubkey))
+        }
+    }
+}
+
+struct FollowingView: View {
+    let contact: NostrEvent
+    let damus_state: DamusState
+    
+    var body: some View {
+        ScrollView {
+            LazyVStack(alignment: .leading) {
+                ForEach(contact.referenced_pubkeys) { pk in
+                    FollowUserView(pubkey: pk.ref_id, damus_state: damus_state)
+                }
+            }
+        }
+    }
+}
+
+/*
+struct FollowingView_Previews: PreviewProvider {
+    static var previews: some View {
+        FollowingView(contact: <#NostrEvent#>, damus_state: <#DamusState#>)
+    }
+}
+ */
diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift
@@ -61,9 +61,8 @@ func perform_follow_btn_action(_ fs: FollowState, target: String) -> FollowState
 }
 
 struct ProfileView: View {
-    let damus: DamusState
+    let damus_state: DamusState
     
-    @State var follow_state: FollowState = .follows
     @State private var selected_tab: ProfileTab = .posts
     @StateObject var profile: ProfileModel
     
@@ -71,32 +70,13 @@ struct ProfileView: View {
     
     var TopSection: some View {
         VStack(alignment: .leading) {
-            let data = damus.profiles.lookup(id: profile.pubkey)
+            let data = damus_state.profiles.lookup(id: profile.pubkey)
             HStack(alignment: .top) {
-                ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE!, highlight: .custom(Color.black, 2), image_cache: damus.image_cache, profiles: damus.profiles)
+                ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE!, highlight: .custom(Color.black, 2), image_cache: damus_state.image_cache, profiles: damus_state.profiles)
                 
                 Spacer()
                 
-                Button("\(follow_btn_txt(follow_state))") {
-                    follow_state = perform_follow_btn_action(follow_state, target: profile.pubkey)
-                }
-                .buttonStyle(.bordered)
-                .onReceive(handle_notify(.followed)) { notif in
-                    let pk = notif.object as! String
-                    if pk != profile.pubkey {
-                        return
-                    }
-                    
-                    self.follow_state = .follows
-                }
-                .onReceive(handle_notify(.unfollowed)) { notif in
-                    let pk = notif.object as! String
-                    if pk != profile.pubkey {
-                        return
-                    }
-                    
-                    self.follow_state = .unfollows
-                }
+                FollowButtonView(pubkey: profile.pubkey, follow_state: damus_state.contacts.follow_state(profile.pubkey))
             }
             
             if let pubkey = profile.pubkey {
@@ -108,7 +88,21 @@ struct ProfileView: View {
                     .font(.footnote)
                     .foregroundColor(id_to_color(pubkey))
             }
+            
             Text(data?.about ?? "")
+            
+            if let contact = profile.contacts {
+                Divider()
+                
+                NavigationLink(destination: FollowingView(contact: contact, damus_state: damus_state)) {
+                    HStack {
+                        Text("\(profile.following)")
+                        Text("Following")
+                            .foregroundColor(.gray)
+                    }
+                }
+                .buttonStyle(PlainButtonStyle())
+            }
         }
     }
     
@@ -119,7 +113,7 @@ struct ProfileView: View {
             
                 Divider()
                 
-                InnerTimelineView(events: $profile.events, damus: damus)
+                InnerTimelineView(events: $profile.events, damus: damus_state)
             }
             .frame(maxHeight: .infinity, alignment: .topLeading)
         }
@@ -128,7 +122,6 @@ struct ProfileView: View {
         
         .navigationBarTitle("Profile")
         .onAppear() {
-            follow_state = damus.contacts.follow_state(profile.pubkey)
             profile.subscribe()
         }
         .onDisappear {