commit 8eb013f1f70b686e146edfa2efb053de0b996cd7
parent 4f459d128a5761143c4ec71af63b56091c95a46b
Author: William Casarin <jb55@jb55.com>
Date:   Tue,  2 May 2023 08:22:36 -0700
Search hashtags automatically
Changelog-Changed: Search hashtags automatically
Diffstat:
2 files changed, 122 insertions(+), 57 deletions(-)
diff --git a/damus/Models/SearchModel.swift b/damus/Models/SearchModel.swift
@@ -87,6 +87,8 @@ class SearchModel: ObservableObject {
             return
         }
         
+        self.loading = false
+        
         if sub_id == self.sub_id {
             load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(self.events.all_events), damus_state: state)
         }
diff --git a/damus/Views/SearchResultsView.swift b/damus/Views/SearchResultsView.swift
@@ -7,82 +7,132 @@
 
 import SwiftUI
 
-enum Search {
+struct MultiSearch {
+    let hashtag: String
+    let profiles: [SearchedUser]
+}
+
+enum Search: Identifiable {
     case profiles([SearchedUser])
     case hashtag(String)
     case profile(String)
     case note(String)
     case nip05(String)
     case hex(String)
+    case multi(MultiSearch)
+    
+    var id: String {
+        switch self {
+        case .profiles: return "profiles"
+        case .hashtag: return "hashtag"
+        case .profile: return "profile"
+        case .note: return "note"
+        case .nip05: return "nip05"
+        case .hex: return "hex"
+        case .multi: return "multi"
+        }
+    }
 }
 
-struct SearchResultsView: View {
+struct AnySearchResultsView: View {
     let damus_state: DamusState
-    @Binding var search: String
+    let searches: [Search]
     
-    @State var result: Search? = nil
+    var body: some View {
+        VStack {
+            ForEach(searches) { r in
+                InnerSearchResults(damus_state: damus_state, search: r)
+            }
+        }
+    }
+}
+
+struct InnerSearchResults: View {
+    let damus_state: DamusState
+    let search: Search?
     
     func ProfileSearchResult(pk: String) -> some View {
         FollowUserView(target: .pubkey(pk), damus_state: damus_state)
     }
     
-    var MainContent: some View {
-        ScrollView {
-            Group {
-                switch result {
-                case .profiles(let results):
-                    LazyVStack {
-                        ForEach(results) { prof in
-                            ProfileSearchResult(pk: prof.pubkey)
-                        }
-                    }
-                case .hashtag(let ht):
-                    let search_model = SearchModel(state: damus_state, search: .filter_hashtag([ht]))
-                    let dst = SearchView(appstate: damus_state, search: search_model)
-                    NavigationLink(destination: dst) {
-                        Text("Search hashtag: #\(ht)", comment: "Navigation link to search hashtag.")
-                    }
-                    
-                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)
-                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)
-                    }
-                    
-                case .note(let nid):
-                    let decoded = try? bech32_decode(nid)
-                    let hex = hex_encode(decoded!.data)
+    func HashtagSearch(_ ht: String) -> some View {
+        let search_model = SearchModel(state: damus_state, search: .filter_hashtag([ht]))
+        let dst = SearchView(appstate: damus_state, search: search_model)
+        return NavigationLink(destination: dst) {
+            Text("Search hashtag: #\(ht)", comment: "Navigation link to search hashtag.")
+        }
+    }
+    
+    func ProfilesSearch(_ results: [SearchedUser]) -> some View {
+        return LazyVStack {
+            ForEach(results) { prof in
+                ProfileSearchResult(pk: prof.pubkey)
+            }
+        }
+    }
+    
+    var body: some View {
+        Group {
+            switch search {
+            case .profiles(let results):
+                ProfilesSearch(results)
+                
+            case .hashtag(let ht):
+                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)
+            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: hex, search_type: .event)
-                case .none:
-                    Text("none", comment: "No search results.")
+                    SearchingEventView(state: damus_state, evid: h, search_type: .profile)
+                }
+                
+            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)
+            case .multi(let multi):
+                VStack {
+                    HashtagSearch(multi.hashtag)
+                    ProfilesSearch(multi.profiles)
                 }
+                
+            case .none:
+                Text("none", comment: "No search results.")
             }
-            .padding()
         }
     }
+}
+
+struct SearchResultsView: View {
+    let damus_state: DamusState
+    @Binding var search: String
+    @State var result: Search? = nil
     
     var body: some View {
-        MainContent
-            .frame(maxHeight: .infinity)
-            .onAppear {
-                self.result = search_for_string(profiles: damus_state.profiles, search)
-            }
-            .onChange(of: search) { new in
-                self.result = search_for_string(profiles: damus_state.profiles, new)
-            }
+        ScrollView {
+            InnerSearchResults(damus_state: damus_state, search: result)
+                .padding()
+        }
+        .frame(maxHeight: .infinity)
+        .onAppear {
+            self.result = search_for_string(profiles: damus_state.profiles, search)
+        }
+        .onChange(of: search) { new in
+            self.result = search_for_string(profiles: damus_state.profiles, new)
+        }
     }
 }
 
@@ -107,8 +157,7 @@ func search_for_string(profiles: Profiles, _ new: String) -> Search? {
     }
     
     if new.first! == "#" {
-        let ht = String(new.dropFirst().filter{$0 != " "})
-        return .hashtag(ht)
+        return .hashtag(make_hashtagable(new))
     }
     
     if hex_decode(new) != nil, new.count == 64 {
@@ -127,7 +176,21 @@ func search_for_string(profiles: Profiles, _ new: String) -> Search? {
         }
     }
     
-    return .profiles(search_profiles(profiles: profiles, search: new))
+    let multisearch = MultiSearch(hashtag: make_hashtagable(new), profiles: search_profiles(profiles: profiles, search: new))
+    return .multi(multisearch)
+}
+
+func make_hashtagable(_ str: String) -> String {
+    var new = str
+    guard str.utf8.count > 0 else {
+        return str
+    }
+    
+    if new.hasPrefix("#") {
+        new = String(new.dropFirst())
+    }
+    
+    return String(new.filter{$0 != " "})
 }
 
 func search_profiles(profiles: Profiles, search: String) -> [SearchedUser] {