Search users in list edit

This commit is contained in:
Thomas Ricouard 2023-11-28 14:16:04 +01:00
parent ab07fb5906
commit f2606b4614
3 changed files with 287 additions and 27 deletions

View file

@ -1,6 +1,22 @@
{ {
"sourceLanguage" : "en", "sourceLanguage" : "en",
"strings" : { "strings" : {
"" : {
"localizations" : {
"be" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
},
"eu" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
}
}
},
" ⸱ " : { " ⸱ " : {
"localizations" : { "localizations" : {
"be" : { "be" : {
@ -31939,6 +31955,125 @@
} }
} }
}, },
"lists.edit.users-search" : {
"extractionState" : "manual",
"localizations" : {
"be" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"en-GB" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"eu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search users"
}
}
}
},
"lists.name" : { "lists.name" : {
"extractionState" : "manual", "extractionState" : "manual",
"localizations" : { "localizations" : {

View file

@ -3,6 +3,7 @@ import EmojiText
import Models import Models
import Network import Network
import SwiftUI import SwiftUI
import Account
@MainActor @MainActor
public struct ListEditView: View { public struct ListEditView: View {
@ -42,12 +43,114 @@ public struct ListEditView: View {
} }
Section("lists.edit.users-in-list") { Section("lists.edit.users-in-list") {
if viewModel.isLoadingAccounts { HStack {
TextField("lists.edit.users-search",
text: $viewModel.searchUserQuery)
if !viewModel.searchUserQuery.isEmpty {
Button {
viewModel.searchUserQuery = ""
} label: {
Image(systemName: "xmark.circle")
}
}
}
.id("stableId")
if !viewModel.searchUserQuery.isEmpty {
searchAccountsView
} else {
listAccountsView
}
}
.listRowBackground(theme.primaryBackgroundColor)
.disabled(viewModel.isUpdating)
}
.scrollDismissesKeyboard(.immediately)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.toolbar {
ToolbarItem {
Button("action.done") {
dismiss()
}
}
}
.navigationTitle(viewModel.list.title)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
viewModel.client = client
Task {
await viewModel.fetchAccounts()
}
}
.task(id: viewModel.searchUserQuery) {
do {
viewModel.isSearching = true
try await Task.sleep(for: .milliseconds(150))
await viewModel.searchUsers()
} catch {}
}
}
}
private var loadingView: some View {
HStack { HStack {
Spacer() Spacer()
ProgressView() ProgressView()
Spacer() Spacer()
} }
.id(UUID())
}
@ViewBuilder
private var searchAccountsView: some View {
if viewModel.isSearching {
loadingView
} else {
ForEach(viewModel.searchedAccounts) { account in
HStack {
AvatarView(account.avatar)
VStack(alignment: .leading) {
EmojiTextApp(.init(stringValue: account.safeDisplayName),
emojis: account.emojis)
.emojiSize(Font.scaledBodyFont.emojiSize)
.emojiBaselineOffset(Font.scaledBodyFont.emojiBaselineOffset)
Text("@\(account.acct)")
.foregroundColor(.gray)
.font(.scaledFootnote)
.lineLimit(1)
}
Spacer()
if let relationship = viewModel.searchedRelationships[account.id] {
if relationship.following {
Toggle("", isOn: .init(get: {
viewModel.accounts.contains(where: { $0.id == account.id })
}, set: { addedToList in
Task {
if addedToList {
await viewModel.add(account: account)
} else {
await viewModel.delete(account: account)
}
}
}))
} else {
FollowButton(viewModel: .init(accountId: account.id,
relationship: relationship,
shouldDisplayNotify: false,
relationshipUpdated: { relationship in
viewModel.searchedRelationships[account.id] = relationship
}))
}
}
}
}
}
}
@ViewBuilder
private var listAccountsView: some View {
if viewModel.isLoadingAccounts {
loadingView
} else { } else {
ForEach(viewModel.accounts) { account in ForEach(viewModel.accounts) { account in
HStack { HStack {
@ -72,25 +175,5 @@ public struct ListEditView: View {
} }
} }
} }
.listRowBackground(theme.primaryBackgroundColor)
}
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.toolbar {
ToolbarItem {
Button("action.done") {
dismiss()
}
}
}
.navigationTitle(viewModel.list.title)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
viewModel.client = client
Task {
await viewModel.fetchAccounts()
}
}
}
}
} }

View file

@ -20,6 +20,11 @@ import Env
var isUpdating: Bool = false var isUpdating: Bool = false
var searchUserQuery: String = ""
var searchedAccounts: [Account] = []
var searchedRelationships: [String: Relationship] = [:]
var isSearching: Bool = false
init(list: Models.List) { init(list: Models.List) {
self.list = list self.list = list
self.title = list.title self.title = list.title
@ -58,14 +63,51 @@ import Env
} }
} }
func add(account: Account) async {
guard let client else { return }
do {
isUpdating = true
let response = try await client.post(endpoint: Lists.updateAccounts(listId: list.id, accounts: [account.id]))
if response?.statusCode == 200 {
accounts.append(account)
}
isUpdating = false
} catch {
isUpdating = false
}
}
func delete(account: Account) async { func delete(account: Account) async {
guard let client else { return } guard let client else { return }
do { do {
isUpdating = true
let response = try await client.delete(endpoint: Lists.updateAccounts(listId: list.id, accounts: [account.id])) let response = try await client.delete(endpoint: Lists.updateAccounts(listId: list.id, accounts: [account.id]))
if response?.statusCode == 200 { if response?.statusCode == 200 {
accounts.removeAll(where: { $0.id == account.id }) accounts.removeAll(where: { $0.id == account.id })
} }
} catch {} isUpdating = false
} catch {
isUpdating = false
}
}
func searchUsers() async {
guard let client, !searchUserQuery.isEmpty else { return }
do {
isSearching = true
let results: SearchResults = try await client.get(endpoint: Search.search(query: searchUserQuery,
type: nil,
offset: nil,
following: nil),
forceVersion: .v2)
let relationships: [Relationship] =
try await client.get(endpoint: Accounts.relationships(ids: results.accounts.map(\.id)))
searchedRelationships = relationships.reduce(into: [String: Relationship]()) {
$0[$1.id] = $1
}
searchedAccounts = results.accounts
isSearching = false
} catch { }
} }
} }