mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-26 10:11:00 +00:00
Fix: Search Instances Feature (#1766)
* fix: search logic and performance * Remove overlay --------- Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
This commit is contained in:
parent
3ac1bf362b
commit
f326bbefe6
3 changed files with 89 additions and 34 deletions
|
@ -28,6 +28,9 @@ 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: LocalizedStringKey?
|
@State private var instanceFetchError: LocalizedStringKey?
|
||||||
|
@State private var instanceSocialClient = InstanceSocialClient()
|
||||||
|
@State private var searchingTask = Task<Void, Never> {}
|
||||||
|
@State private var getInstanceDetailTask = Task<Void, Never> {}
|
||||||
|
|
||||||
private let instanceNamePublisher = PassthroughSubject<String, Never>()
|
private let instanceNamePublisher = PassthroughSubject<String, Never>()
|
||||||
|
|
||||||
|
@ -93,28 +96,39 @@ struct AddAccountView: View {
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
isInstanceURLFieldFocused = true
|
isInstanceURLFieldFocused = true
|
||||||
let client = InstanceSocialClient()
|
|
||||||
Task {
|
Task {
|
||||||
let instances = await client.fetchInstances()
|
let instances = await instanceSocialClient.fetchInstances(keyword: instanceName)
|
||||||
withAnimation {
|
withAnimation {
|
||||||
self.instances = instances
|
self.instances = instances
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isSigninIn = false
|
isSigninIn = false
|
||||||
}
|
}
|
||||||
.onChange(of: instanceName) { _, newValue in
|
.onChange(of: instanceName) {
|
||||||
instanceNamePublisher.send(newValue)
|
searchingTask.cancel()
|
||||||
}
|
searchingTask = Task {
|
||||||
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { _ in
|
try? await Task.sleep(for: .seconds(0.1))
|
||||||
// let newValue = newValue
|
guard !Task.isCancelled else { return }
|
||||||
// .replacingOccurrences(of: "http://", with: "")
|
|
||||||
// .replacingOccurrences(of: "https://", with: "")
|
let instances = await instanceSocialClient.fetchInstances(keyword: instanceName)
|
||||||
let client = Client(server: sanitizedName)
|
withAnimation {
|
||||||
Task {
|
self.instances = instances
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstanceDetailTask.cancel()
|
||||||
|
getInstanceDetailTask = Task {
|
||||||
|
try? await Task.sleep(for: .seconds(0.1))
|
||||||
|
guard !Task.isCancelled else { return }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// bare bones preflight for domain validity
|
// bare bones preflight for domain validity
|
||||||
if client.server.contains("."), client.server.last != "." {
|
let instanceDetailClient = Client(server: sanitizedName)
|
||||||
let instance: Instance = try await client.get(endpoint: Instances.instance)
|
if
|
||||||
|
instanceDetailClient.server.contains("."),
|
||||||
|
instanceDetailClient.server.last != "."
|
||||||
|
{
|
||||||
|
let instance: Instance = try await instanceDetailClient.get(endpoint: Instances.instance)
|
||||||
withAnimation {
|
withAnimation {
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
|
instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
|
||||||
|
@ -178,7 +192,7 @@ struct AddAccountView: View {
|
||||||
if instances.isEmpty {
|
if instances.isEmpty {
|
||||||
placeholderRow
|
placeholderRow
|
||||||
} else {
|
} else {
|
||||||
ForEach(sanitizedName.isEmpty ? instances : instances.filter { $0.name.contains(sanitizedName.lowercased()) }) { instance in
|
ForEach(instances) { instance in
|
||||||
Button {
|
Button {
|
||||||
instanceName = instance.name
|
instanceName = instance.name
|
||||||
} label: {
|
} label: {
|
||||||
|
@ -221,13 +235,8 @@ struct AddAccountView: View {
|
||||||
.listRowBackground(Color.clear)
|
.listRowBackground(Color.clear)
|
||||||
.listRowInsets(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
|
.listRowInsets(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
|
||||||
.listRowSeparator(.hidden)
|
.listRowSeparator(.hidden)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||||
#endif
|
#endif
|
||||||
.overlay {
|
|
||||||
RoundedRectangle(cornerRadius: 5)
|
|
||||||
.stroke(lineWidth: 1)
|
|
||||||
.fill(theme.tintColor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ struct AddRemoteTimelineView: View {
|
||||||
isInstanceURLFieldFocused = true
|
isInstanceURLFieldFocused = true
|
||||||
let client = InstanceSocialClient()
|
let client = InstanceSocialClient()
|
||||||
Task {
|
Task {
|
||||||
instances = await client.fetchInstances()
|
instances = await client.fetchInstances(keyword: instanceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@ import Models
|
||||||
|
|
||||||
public struct InstanceSocialClient {
|
public struct InstanceSocialClient {
|
||||||
private let authorization = "Bearer 8a4xx3D7Hzu1aFnf18qlkH8oU0oZ5ulabXxoS2FtQtwOy8G0DGQhr5PjTIjBnYAmFrSBuE2CcASjFocxJBonY8XGbLySB7MXd9ssrwlRHUXTQh3Z578lE1OfUtafvhML"
|
private let authorization = "Bearer 8a4xx3D7Hzu1aFnf18qlkH8oU0oZ5ulabXxoS2FtQtwOy8G0DGQhr5PjTIjBnYAmFrSBuE2CcASjFocxJBonY8XGbLySB7MXd9ssrwlRHUXTQh3Z578lE1OfUtafvhML"
|
||||||
private let endpoint = URL(string: "https://instances.social/api/1.0/instances/list?count=1000&include_closed=false&include_dead=false&min_active_users=500")!
|
private let listEndpoint = "https://instances.social/api/1.0/instances/list?count=1000&include_closed=false&include_dead=false&min_active_users=500"
|
||||||
|
private let searchEndpoint = "https://instances.social/api/1.0/instances/search"
|
||||||
|
|
||||||
struct Response: Decodable {
|
struct Response: Decodable {
|
||||||
let instances: [InstanceSocial]
|
let instances: [InstanceSocial]
|
||||||
|
@ -11,17 +12,62 @@ public struct InstanceSocialClient {
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public func fetchInstances() async -> [InstanceSocial] {
|
public func fetchInstances(keyword: String) async -> [InstanceSocial] {
|
||||||
do {
|
let keyword = keyword.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let decoder = JSONDecoder()
|
|
||||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
let endpoint = keyword.isEmpty ? listEndpoint : searchEndpoint + "?q=\(keyword)"
|
||||||
var request: URLRequest = .init(url: endpoint)
|
|
||||||
request.setValue(authorization, forHTTPHeaderField: "Authorization")
|
guard let url = URL(string: endpoint) else { return [] }
|
||||||
let (data, _) = try await URLSession.shared.data(for: request)
|
|
||||||
let response = try decoder.decode(Response.self, from: data)
|
var request = URLRequest(url: url)
|
||||||
return response.instances
|
request.setValue(authorization, forHTTPHeaderField: "Authorization")
|
||||||
} catch {
|
|
||||||
return []
|
guard let (data, _) = try? await URLSession.shared.data(for: request) else { return [] }
|
||||||
}
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
|
||||||
|
guard let response = try? decoder.decode(Response.self, from: data) else { return [] }
|
||||||
|
|
||||||
|
let result = response.instances.sorted(by: keyword)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Self.Element == InstanceSocial {
|
||||||
|
fileprivate func sorted(by keyword: String) -> Self {
|
||||||
|
let keyword = keyword.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
var newArray = self
|
||||||
|
|
||||||
|
newArray.sort { (lhs: InstanceSocial, rhs: InstanceSocial) in
|
||||||
|
guard
|
||||||
|
let lhsNumber = Int(lhs.users),
|
||||||
|
let rhsNumber = Int(rhs.users)
|
||||||
|
else { return false }
|
||||||
|
|
||||||
|
return lhsNumber > rhsNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
newArray.sort { (lhs: InstanceSocial, rhs: InstanceSocial) in
|
||||||
|
guard
|
||||||
|
let lhsNumber = Int(lhs.statuses),
|
||||||
|
let rhsNumber = Int(rhs.statuses)
|
||||||
|
else { return false }
|
||||||
|
|
||||||
|
return lhsNumber > rhsNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
if !keyword.isEmpty {
|
||||||
|
newArray.sort { (lhs: InstanceSocial, rhs: InstanceSocial) in
|
||||||
|
if
|
||||||
|
lhs.name.contains(keyword),
|
||||||
|
!rhs.name.contains(keyword)
|
||||||
|
{ return true }
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newArray
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue