Explore: Search

This commit is contained in:
Thomas Ricouard 2022-12-27 10:04:39 +01:00
parent a84d3da19a
commit 816e1d5e7d
6 changed files with 121 additions and 39 deletions

View file

@ -84,7 +84,7 @@ struct SettingsTabs: View {
LabeledContent("Email", value: instanceData.email)
LabeledContent("Version", value: instanceData.version)
LabeledContent("Users", value: "\(instanceData.stats.userCount)")
LabeledContent("Status", value: "\(instanceData.stats.statusCount)")
LabeledContent("Posts", value: "\(instanceData.stats.statusCount)")
LabeledContent("Domains", value: "\(instanceData.stats.domainCount)")
}
}

View file

@ -18,6 +18,7 @@ public class AccountsListRowViewModel: ObservableObject {
}
public struct AccountsListRow: View {
@EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var routeurPath: RouterPath
@EnvironmentObject private var client: Client
@ -45,8 +46,10 @@ public struct AccountsListRow: View {
})
}
Spacer()
FollowButton(viewModel: .init(accountId: viewModel.account.id,
relationship: viewModel.relationShip))
if currentAccount.account?.id != viewModel.account.id {
FollowButton(viewModel: .init(accountId: viewModel.account.id,
relationship: viewModel.relationShip))
}
}
.onAppear {
viewModel.client = client

View file

@ -12,26 +12,29 @@ extension Account {
}
public var displayNameWithEmojis: some View {
let splittedDisplayName = displayName.split(separator: ":").map{ Part(value: $0) }
return HStack(spacing: 0) {
ForEach(splittedDisplayName, id: \.id) { part in
if let emoji = emojis.first(where: { $0.shortcode == part.value }) {
LazyImage(url: emoji.url) { state in
if let image = state.image {
image
.resizingMode(.aspectFit)
} else if state.isLoading {
ProgressView()
} else {
ProgressView()
}
}
.processors([ImageProcessors.Resize(size: .init(width: 20, height: 20))])
.frame(width: 20, height: 20)
} else {
Text(part.value)
}
}
}
}
let splittedDisplayName = displayName.split(separator: ":").map{ Part(value: $0) }
return HStack(spacing: 0) {
if displayName.isEmpty {
Text(" ")
}
ForEach(splittedDisplayName, id: \.id) { part in
if let emoji = emojis.first(where: { $0.shortcode == part.value }) {
LazyImage(url: emoji.url) { state in
if let image = state.image {
image
.resizingMode(.aspectFit)
} else if state.isLoading {
ProgressView()
} else {
ProgressView()
}
}
.processors([ImageProcessors.Resize(size: .init(width: 20, height: 20))])
.frame(width: 20, height: 20)
} else {
Text(part.value)
}
}
}
}
}

View file

@ -18,13 +18,14 @@ public struct ExploreView: View {
public var body: some View {
List {
if !viewModel.isLoaded {
ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isEmbed: false))
.padding(.vertical, 8)
.redacted(reason: .placeholder)
.shimmering()
if !viewModel.searchQuery.isEmpty {
if let results = viewModel.results[viewModel.searchQuery] {
makeSearchResultsView(results: results)
} else {
loadingView
}
} else if !viewModel.isLoaded {
loadingView
} else {
trendingTagsSection
suggestedAccountsSection
@ -53,6 +54,44 @@ public struct ExploreView: View {
})
}
private var loadingView: some View {
ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isEmbed: false))
.padding(.vertical, 8)
.redacted(reason: .placeholder)
.shimmering()
}
}
@ViewBuilder
private func makeSearchResultsView(results: SearchResults) -> some View {
if !results.accounts.isEmpty {
Section("Users") {
ForEach(results.accounts) { account in
if let relationship = results.relationships.first(where: { $0.id == account.id }) {
AccountsListRow(viewModel: .init(account: account, relationShip: relationship))
}
}
}
}
if !results.hashtags.isEmpty {
Section("Tags") {
ForEach(results.hashtags) { tag in
TagRowView(tag: tag)
.padding(.vertical, 4)
}
}
}
if !results.statuses.isEmpty {
Section("Posts") {
ForEach(results.statuses) { status in
StatusRowView(viewModel: .init(status: status))
.padding(.vertical, 8)
}
}
}
}
private var suggestedAccountsSection: some View {
Section("Suggested Users") {
ForEach(viewModel.suggestedAccounts

View file

@ -7,11 +7,24 @@ class ExploreViewModel: ObservableObject {
var client: Client?
enum Token: String, Identifiable {
case user = "@user", tag = "#hasgtag"
case user = "@user"
case statuses = "@posts"
case tag = "#hasgtag"
var id: String {
rawValue
}
var apiType: String {
switch self {
case .user:
return "accounts"
case .tag:
return "hashtags"
case .statuses:
return "statuses"
}
}
}
@Published var tokens: [Token] = []
@ -19,11 +32,14 @@ class ExploreViewModel: ObservableObject {
@Published var searchQuery = "" {
didSet {
if searchQuery.starts(with: "@") {
suggestedToken = [.user]
suggestedToken = [.user, .statuses]
} else if searchQuery.starts(with: "#") {
suggestedToken = [.tag]
} else if tokens.isEmpty {
} else if !tokens.isEmpty {
suggestedToken = []
search()
} else {
search()
}
}
}
@ -35,10 +51,13 @@ class ExploreViewModel: ObservableObject {
@Published var trendingStatuses: [Status] = []
@Published var trendingLinks: [Card] = []
private var searchTask: Task<Void, Never>?
func fetchTrending() async {
guard let client else { return }
do {
isLoaded = false
async let suggestedAccounts: [Account] = client.get(endpoint: Accounts.suggestions)
async let trendingTags: [Tag] = client.get(endpoint: Trends.tags)
async let trendingStatuses: [Status] = client.get(endpoint: Trends.statuses)
@ -55,10 +74,23 @@ class ExploreViewModel: ObservableObject {
} catch { }
}
func search() async {
guard let client else { return }
do {
results[searchQuery] = try await client.get(endpoint: Search.search(query: searchQuery, type: nil, offset: nil), forceVersion: .v2)
} catch { }
func search() {
guard !searchQuery.isEmpty else { return }
searchTask?.cancel()
searchTask = nil
searchTask = Task {
guard let client else { return }
do {
let apiType = tokens.first?.apiType
var results: SearchResults = try await client.get(endpoint: Search.search(query: searchQuery,
type: apiType,
offset: nil),
forceVersion: .v2)
let relationships: [Relationshionship] =
try await client.get(endpoint: Accounts.relationships(ids: results.accounts.map{ $0.id }))
results.relationships = relationships
self.results[searchQuery] = results
} catch { }
}
}
}

View file

@ -1,7 +1,12 @@
import Foundation
public struct SearchResults: Decodable {
enum CodingKeys: String, CodingKey {
case accounts, statuses, hashtags
}
public let accounts: [Account]
public var relationships: [Relationshionship] = []
public let statuses: [Status]
public let hashtags: [Tag]
}