Search: Completely revamp it! close #78 #90

This commit is contained in:
Thomas Ricouard 2023-01-21 07:51:15 +01:00
parent eb1925b5d5
commit 7b25240f59
7 changed files with 44 additions and 51 deletions

View File

@ -180,6 +180,8 @@
"explore.search.message-%@" = "Hier kannst du alles auf %@ suchen"; "explore.search.message-%@" = "Hier kannst du alles auf %@ suchen";
"explore.search.prompt" = "Suche nach Usern, Posts und Hashtags"; "explore.search.prompt" = "Suche nach Usern, Posts und Hashtags";
"explore.search.title" = "Durchsuche deine Instanz"; "explore.search.title" = "Durchsuche deine Instanz";
"explore.search.empty.message" = "This query returned no search results, please try another one.";
"explore.search.empty.title" = "No search results";
"explore.section.posts" = "Posts"; "explore.section.posts" = "Posts";
"explore.section.suggested-users" = "Vorgeschlagene Nutzer:innen"; "explore.section.suggested-users" = "Vorgeschlagene Nutzer:innen";
"explore.section.tags" = "Tags"; "explore.section.tags" = "Tags";

View File

@ -180,6 +180,8 @@
"explore.search.message-%@" = "From this screen you can search anything on %@"; "explore.search.message-%@" = "From this screen you can search anything on %@";
"explore.search.prompt" = "Search users, posts and tags"; "explore.search.prompt" = "Search users, posts and tags";
"explore.search.title" = "Search your instance"; "explore.search.title" = "Search your instance";
"explore.search.empty.message" = "This query returned no search results, please try another one.";
"explore.search.empty.title" = "No search results";
"explore.section.posts" = "Posts"; "explore.section.posts" = "Posts";
"explore.section.suggested-users" = "Suggested Users"; "explore.section.suggested-users" = "Suggested Users";
"explore.section.tags" = "Tags"; "explore.section.tags" = "Tags";

View File

@ -179,6 +179,8 @@
"explore.search.message-%@" = "Desde esta pantalla puedes buscar cualquier cosa en %@"; "explore.search.message-%@" = "Desde esta pantalla puedes buscar cualquier cosa en %@";
"explore.search.prompt" = "Busca usuarios, publicaciones y etiquetas"; "explore.search.prompt" = "Busca usuarios, publicaciones y etiquetas";
"explore.search.title" = "Busca en tu instancia"; "explore.search.title" = "Busca en tu instancia";
"explore.search.empty.message" = "This query returned no search results, please try another one.";
"explore.search.empty.title" = "No search results";
"explore.section.posts" = "Publicaciones"; "explore.section.posts" = "Publicaciones";
"explore.section.suggested-users" = "Sugerencias de usuarios"; "explore.section.suggested-users" = "Sugerencias de usuarios";
"explore.section.tags" = "Etiquetas"; "explore.section.tags" = "Etiquetas";

View File

@ -180,6 +180,8 @@
"explore.search.message-%@" = "Vanaf dit scherm kan je naar alles zoeken op %@"; "explore.search.message-%@" = "Vanaf dit scherm kan je naar alles zoeken op %@";
"explore.search.prompt" = "Zoek gebruikers, posts en hashtags"; "explore.search.prompt" = "Zoek gebruikers, posts en hashtags";
"explore.search.title" = "Doorzoek je instantie"; "explore.search.title" = "Doorzoek je instantie";
"explore.search.empty.message" = "This query returned no search results, please try another one.";
"explore.search.empty.title" = "No search results";
"explore.section.posts" = "Posts"; "explore.section.posts" = "Posts";
"explore.section.suggested-users" = "Gesuggereerde Gebruikers"; "explore.section.suggested-users" = "Gesuggereerde Gebruikers";
"explore.section.tags" = "Hashtags"; "explore.section.tags" = "Hashtags";

View File

@ -18,19 +18,28 @@ public struct ExploreView: View {
public var body: some View { public var body: some View {
List { List {
if !viewModel.searchQuery.isEmpty { if !viewModel.isLoaded {
if let results = viewModel.results[viewModel.searchQuery] { loadingView
} else if !viewModel.searchQuery.isEmpty {
if viewModel.isSearching {
HStack { }
.listRowBackground(theme.secondaryBackgroundColor)
.listRowSeparator(.hidden)
} else if let results = viewModel.results[viewModel.searchQuery], !results.isEmpty {
makeSearchResultsView(results: results) makeSearchResultsView(results: results)
} else { } else {
loadingView EmptyView(iconName: "magnifyingglass",
title: "explore.search.empty.title",
message: "explore.search.empty.message")
.listRowBackground(theme.secondaryBackgroundColor)
.listRowSeparator(.hidden)
} }
} else if !viewModel.isLoaded {
loadingView
} else if viewModel.allSectionsEmpty { } else if viewModel.allSectionsEmpty {
EmptyView(iconName: "magnifyingglass", EmptyView(iconName: "magnifyingglass",
title: "explore.search.title", title: "explore.search.title",
message: "explore.search.message-\(client.server)") message: "explore.search.message-\(client.server)")
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
.listRowSeparator(.hidden)
} else { } else {
if !viewModel.trendingTags.isEmpty { if !viewModel.trendingTags.isEmpty {
trendingTagsSection trendingTagsSection
@ -60,12 +69,7 @@ public struct ExploreView: View {
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.navigationTitle("explore.navigation-title") .navigationTitle("explore.navigation-title")
.searchable(text: $viewModel.searchQuery, .searchable(text: $viewModel.searchQuery,
tokens: $viewModel.tokens, prompt: Text("explore.search.prompt"))
suggestedTokens: $viewModel.suggestedToken,
prompt: Text("explore.search.prompt"),
token: { token in
Text(token.rawValue)
})
} }
private var loadingView: some View { private var loadingView: some View {

View File

@ -18,36 +18,18 @@ class ExploreViewModel: ObservableObject {
} }
} }
enum Token: String, Identifiable {
case user = "@user"
case statuses = "@posts"
case tag = "#hashtag"
var id: String {
rawValue
}
var apiType: String {
switch self {
case .user:
return "accounts"
case .tag:
return "hashtags"
case .statuses:
return "statuses"
}
}
}
var allSectionsEmpty: Bool { var allSectionsEmpty: Bool {
trendingLinks.isEmpty && trendingTags.isEmpty && trendingStatuses.isEmpty && suggestedAccounts.isEmpty trendingLinks.isEmpty && trendingTags.isEmpty && trendingStatuses.isEmpty && suggestedAccounts.isEmpty
} }
@Published var tokens: [Token] = [] @Published var searchQuery = "" {
@Published var suggestedToken: [Token] = [] didSet {
@Published var searchQuery = "" isSearching = true
}
}
@Published var results: [String: SearchResults] = [:] @Published var results: [String: SearchResults] = [:]
@Published var isLoaded = false @Published var isLoaded = false
@Published var isSearching = false
@Published var suggestedAccounts: [Account] = [] @Published var suggestedAccounts: [Account] = []
@Published var suggestedAccountsRelationShips: [Relationship] = [] @Published var suggestedAccountsRelationShips: [Relationship] = []
@Published var trendingTags: [Tag] = [] @Published var trendingTags: [Tag] = []
@ -60,19 +42,9 @@ class ExploreViewModel: ObservableObject {
init() { init() {
$searchQuery $searchQuery
.removeDuplicates() .removeDuplicates()
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main) .debounce(for: .milliseconds(250), scheduler: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in .sink(receiveValue: { [weak self] _ in
guard let self else { return } self?.search()
if self.searchQuery.starts(with: "@") {
self.suggestedToken = [.user, .statuses]
} else if self.searchQuery.starts(with: "#") {
self.suggestedToken = [.tag]
} else {
self.suggestedToken = []
}
self.search()
}) })
.store(in: &cancellables) .store(in: &cancellables)
} }
@ -115,22 +87,27 @@ class ExploreViewModel: ObservableObject {
func search() { func search() {
guard !searchQuery.isEmpty else { return } guard !searchQuery.isEmpty else { return }
isSearching = true
searchTask?.cancel() searchTask?.cancel()
searchTask = nil searchTask = nil
searchTask = Task { searchTask = Task {
guard let client else { return } guard let client else { return }
do { do {
let apiType = tokens.first?.apiType
var results: SearchResults = try await client.get(endpoint: Search.search(query: searchQuery, var results: SearchResults = try await client.get(endpoint: Search.search(query: searchQuery,
type: apiType, type: nil,
offset: nil, offset: nil,
following: nil), following: nil),
forceVersion: .v2) forceVersion: .v2)
let relationships: [Relationship] = let relationships: [Relationship] =
try await client.get(endpoint: Accounts.relationships(ids: results.accounts.map { $0.id })) try await client.get(endpoint: Accounts.relationships(ids: results.accounts.map { $0.id }))
results.relationships = relationships results.relationships = relationships
self.results[searchQuery] = results withAnimation {
} catch {} self.results[searchQuery] = results
isSearching = false
}
} catch {
isSearching = false
}
} }
} }
} }

View File

@ -9,4 +9,8 @@ public struct SearchResults: Decodable {
public var relationships: [Relationship] = [] public var relationships: [Relationship] = []
public let statuses: [Status] public let statuses: [Status]
public let hashtags: [Tag] public let hashtags: [Tag]
public var isEmpty: Bool {
accounts.isEmpty && statuses.isEmpty && hashtags.isEmpty
}
} }