mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-29 03:31:02 +00:00
Search users in list edit
This commit is contained in:
parent
ab07fb5906
commit
f2606b4614
3 changed files with 287 additions and 27 deletions
|
@ -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" : {
|
||||||
|
|
|
@ -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,38 +43,28 @@ public struct ListEditView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("lists.edit.users-in-list") {
|
Section("lists.edit.users-in-list") {
|
||||||
if viewModel.isLoadingAccounts {
|
HStack {
|
||||||
HStack {
|
TextField("lists.edit.users-search",
|
||||||
Spacer()
|
text: $viewModel.searchUserQuery)
|
||||||
ProgressView()
|
if !viewModel.searchUserQuery.isEmpty {
|
||||||
Spacer()
|
Button {
|
||||||
}
|
viewModel.searchUserQuery = ""
|
||||||
} else {
|
} label: {
|
||||||
ForEach(viewModel.accounts) { account in
|
Image(systemName: "xmark.circle")
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onDelete { indexes in
|
|
||||||
if let index = indexes.first {
|
|
||||||
Task {
|
|
||||||
let account = viewModel.accounts[index]
|
|
||||||
await viewModel.delete(account: account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.id("stableId")
|
||||||
|
if !viewModel.searchUserQuery.isEmpty {
|
||||||
|
searchAccountsView
|
||||||
|
} else {
|
||||||
|
listAccountsView
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
.disabled(viewModel.isUpdating)
|
||||||
}
|
}
|
||||||
|
.scrollDismissesKeyboard(.immediately)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(theme.secondaryBackgroundColor)
|
.background(theme.secondaryBackgroundColor)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
@ -91,6 +82,98 @@ public struct ListEditView: View {
|
||||||
await viewModel.fetchAccounts()
|
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 {
|
||||||
|
Spacer()
|
||||||
|
ProgressView()
|
||||||
|
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 {
|
||||||
|
ForEach(viewModel.accounts) { 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onDelete { indexes in
|
||||||
|
if let index = indexes.first {
|
||||||
|
Task {
|
||||||
|
let account = viewModel.accounts[index]
|
||||||
|
await viewModel.delete(account: account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue