Less chatty api calling (#79)

* Limit API calls for instance searching

* Limit api calls

* Fix empty/initial state

* Limit API calls

* Delegate empty view logic to viewmodel

* When you boosted, display You boosted
This commit is contained in:
Sean Goldin 2023-01-15 23:43:53 -06:00 committed by GitHub
parent a49175fe69
commit 75e9516089
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 48 additions and 20 deletions

View file

@ -6,6 +6,7 @@ import DesignSystem
import NukeUI import NukeUI
import Shimmer import Shimmer
import AppAccount import AppAccount
import Combine
struct AddAccountView: View { struct AddAccountView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -23,6 +24,8 @@ struct AddAccountView: View {
@State private var signInClient: Client? @State private var signInClient: Client?
@State private var instances: [InstanceSocial] = [] @State private var instances: [InstanceSocial] = []
@State private var instanceFetchError: String? @State private var instanceFetchError: String?
private let instanceNamePublisher = PassthroughSubject<String, Never>()
@FocusState private var isInstanceURLFieldFocused: Bool @FocusState private var isInstanceURLFieldFocused: Bool
@ -68,6 +71,9 @@ struct AddAccountView: View {
isSigninIn = false isSigninIn = false
} }
.onChange(of: instanceName) { newValue in .onChange(of: instanceName) { newValue in
instanceNamePublisher.send(newValue)
}
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in
let client = Client(server: newValue) let client = Client(server: newValue)
Task { Task {
do { do {

View file

@ -5,6 +5,7 @@ import Env
import DesignSystem import DesignSystem
import NukeUI import NukeUI
import Shimmer import Shimmer
import Combine
struct AddRemoteTimelineView: View { struct AddRemoteTimelineView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -15,6 +16,8 @@ struct AddRemoteTimelineView: View {
@State private var instanceName: String = "" @State private var instanceName: String = ""
@State private var instance: Instance? @State private var instance: Instance?
@State private var instances: [InstanceSocial] = [] @State private var instances: [InstanceSocial] = []
private let instanceNamePublisher = PassthroughSubject<String, Never>()
@FocusState private var isInstanceURLFieldFocused: Bool @FocusState private var isInstanceURLFieldFocused: Bool
@ -55,12 +58,15 @@ struct AddRemoteTimelineView: View {
Button("Cancel", action: { dismiss() }) Button("Cancel", action: { dismiss() })
} }
} }
.onChange(of: instanceName, perform: { newValue in .onChange(of: instanceName) { newValue in
instanceNamePublisher.send(newValue)
}
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in
Task { Task {
let client = Client(server: newValue) let client = Client(server: newValue)
instance = try? await client.get(endpoint: Instances.instance) instance = try? await client.get(endpoint: Instances.instance)
} }
}) }
.onAppear { .onAppear {
isInstanceURLFieldFocused = true isInstanceURLFieldFocused = true
let client = InstanceSocialClient() let client = InstanceSocialClient()

View file

@ -26,10 +26,7 @@ public struct ExploreView: View {
} }
} else if !viewModel.isLoaded { } else if !viewModel.isLoaded {
loadingView loadingView
} else if viewModel.trendingLinks.isEmpty && } else if viewModel.allSectionsEmpty {
viewModel.trendingTags.isEmpty &&
viewModel.trendingStatuses.isEmpty &&
viewModel.suggestedAccounts.isEmpty {
EmptyView(iconName: "magnifyingglass", EmptyView(iconName: "magnifyingglass",
title: "Search your instance", title: "Search your instance",
message: "From this screen you can search anything on \(client.server)") message: "From this screen you can search anything on \(client.server)")

View file

@ -1,6 +1,7 @@
import SwiftUI import SwiftUI
import Models import Models
import Network import Network
import Combine
@MainActor @MainActor
class ExploreViewModel: ObservableObject { class ExploreViewModel: ObservableObject {
@ -37,21 +38,14 @@ class ExploreViewModel: ObservableObject {
} }
} }
} }
var allSectionsEmpty: Bool {
trendingLinks.isEmpty && trendingTags.isEmpty && trendingStatuses.isEmpty && suggestedAccounts.isEmpty
}
@Published var tokens: [Token] = [] @Published var tokens: [Token] = []
@Published var suggestedToken: [Token] = [] @Published var suggestedToken: [Token] = []
@Published var searchQuery = "" { @Published var searchQuery = ""
didSet {
if searchQuery.starts(with: "@") {
suggestedToken = [.user, .statuses]
} else if searchQuery.starts(with: "#") {
suggestedToken = [.tag]
} else {
suggestedToken = []
}
search()
}
}
@Published var results: [String: SearchResults] = [:] @Published var results: [String: SearchResults] = [:]
@Published var isLoaded = false @Published var isLoaded = false
@Published var suggestedAccounts: [Account] = [] @Published var suggestedAccounts: [Account] = []
@ -61,6 +55,27 @@ class ExploreViewModel: ObservableObject {
@Published var trendingLinks: [Card] = [] @Published var trendingLinks: [Card] = []
private var searchTask: Task<Void, Never>? private var searchTask: Task<Void, Never>?
private var cancellables = Set<AnyCancellable>()
init() {
$searchQuery
.removeDuplicates()
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.sink(receiveValue: { [weak self] newValue in
guard let self else { return }
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)
}
func fetchTrending() async { func fetchTrending() async {
guard let client else { return } guard let client else { return }

View file

@ -98,8 +98,12 @@ public struct StatusRowView: View {
HStack(spacing: 2) { HStack(spacing: 2) {
Image(systemName:"arrow.left.arrow.right.circle.fill") Image(systemName:"arrow.left.arrow.right.circle.fill")
AvatarView(url: viewModel.status.account.avatar, size: .boost) AvatarView(url: viewModel.status.account.avatar, size: .boost)
EmojiTextApp(viewModel.status.account.safeDisplayName.asMarkdown, emojis: viewModel.status.account.emojis) if viewModel.status.account.username != account.account?.username {
Text("boosted") EmojiTextApp(viewModel.status.account.safeDisplayName.asMarkdown, emojis: viewModel.status.account.emojis)
Text("boosted")
} else {
Text("You boosted")
}
} }
.font(.footnote) .font(.footnote)
.foregroundColor(.gray) .foregroundColor(.gray)