mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-26 02:01:02 +00:00
Explore: Search
This commit is contained in:
parent
a84d3da19a
commit
816e1d5e7d
6 changed files with 121 additions and 39 deletions
|
@ -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)")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue