From 816e1d5e7d3b2c2b5da71385fd794b635a799141 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Tue, 27 Dec 2022 10:04:39 +0100 Subject: [PATCH] Explore: Search --- .../App/Tabs/Settings/SettingsTab.swift | 2 +- .../AccountsLIst/AccountsListRow.swift | 7 ++- .../Sources/DesignSystem/AccountExt.swift | 47 +++++++++-------- .../Explore/Sources/Explore/ExploreView.swift | 51 ++++++++++++++++--- .../Sources/Explore/ExploreViewModel.swift | 48 ++++++++++++++--- .../Models/Sources/Models/SearchResults.swift | 5 ++ 6 files changed, 121 insertions(+), 39 deletions(-) diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index 27bc488b..86587729 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -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)") } } diff --git a/Packages/Account/Sources/Account/AccountsLIst/AccountsListRow.swift b/Packages/Account/Sources/Account/AccountsLIst/AccountsListRow.swift index cb321350..b07ea302 100644 --- a/Packages/Account/Sources/Account/AccountsLIst/AccountsListRow.swift +++ b/Packages/Account/Sources/Account/AccountsLIst/AccountsListRow.swift @@ -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 diff --git a/Packages/DesignSystem/Sources/DesignSystem/AccountExt.swift b/Packages/DesignSystem/Sources/DesignSystem/AccountExt.swift index 5caef409..31f57a9e 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/AccountExt.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/AccountExt.swift @@ -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) + } + } + } + } } diff --git a/Packages/Explore/Sources/Explore/ExploreView.swift b/Packages/Explore/Sources/Explore/ExploreView.swift index 43ed6a00..a566d768 100644 --- a/Packages/Explore/Sources/Explore/ExploreView.swift +++ b/Packages/Explore/Sources/Explore/ExploreView.swift @@ -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 diff --git a/Packages/Explore/Sources/Explore/ExploreViewModel.swift b/Packages/Explore/Sources/Explore/ExploreViewModel.swift index 95d04462..a7d6569f 100644 --- a/Packages/Explore/Sources/Explore/ExploreViewModel.swift +++ b/Packages/Explore/Sources/Explore/ExploreViewModel.swift @@ -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? + 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 { } + } } } diff --git a/Packages/Models/Sources/Models/SearchResults.swift b/Packages/Models/Sources/Models/SearchResults.swift index 836938c6..8bb759da 100644 --- a/Packages/Models/Sources/Models/SearchResults.swift +++ b/Packages/Models/Sources/Models/SearchResults.swift @@ -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] }