Reworked account management / selection UI

This commit is contained in:
Thomas Ricouard 2023-02-16 07:19:20 +01:00
parent 6c6d25fc63
commit 40ca3940f6
5 changed files with 89 additions and 29 deletions

View file

@ -21,6 +21,7 @@ struct SettingsTabs: View {
@StateObject private var routerPath = RouterPath() @StateObject private var routerPath = RouterPath()
@State private var addAccountSheetPresented = false @State private var addAccountSheetPresented = false
@State private var isEditingAccount = false
@Binding var popToRootTab: Tab @Binding var popToRootTab: Tab
@ -69,27 +70,47 @@ struct SettingsTabs: View {
private var accountsSection: some View { private var accountsSection: some View {
Section("settings.section.accounts") { Section("settings.section.accounts") {
ForEach(appAccountsManager.availableAccounts) { account in ForEach(appAccountsManager.availableAccounts) { account in
HStack {
if isEditingAccount {
Button {
Task {
await logoutAccount(account: account)
}
} label: {
Image(systemName: "trash")
.renderingMode(.template)
.tint(.red)
}
}
AppAccountView(viewModel: .init(appAccount: account)) AppAccountView(viewModel: .init(appAccount: account))
} }
}
.onDelete { indexSet in .onDelete { indexSet in
if let index = indexSet.first { if let index = indexSet.first {
let account = appAccountsManager.availableAccounts[index] let account = appAccountsManager.availableAccounts[index]
Task {
await logoutAccount(account: account)
}
}
}
if !appAccountsManager.availableAccounts.isEmpty {
editAccountButton
}
addAccountButton
}
.listRowBackground(theme.primaryBackgroundColor)
}
private func logoutAccount(account: AppAccount) async {
if let token = account.oauthToken, if let token = account.oauthToken,
let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token }) let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token })
{ {
Task {
let client = Client(server: account.server, oauthToken: token) let client = Client(server: account.server, oauthToken: token)
await TimelineCache.shared.clearCache(for: client) await TimelineCache.shared.clearCache(for: client)
await sub.deleteSubscription() await sub.deleteSubscription()
appAccountsManager.delete(account: account) appAccountsManager.delete(account: account)
} }
} }
}
}
addAccountButton
}
.listRowBackground(theme.primaryBackgroundColor)
}
@ViewBuilder @ViewBuilder
private var generalSection: some View { private var generalSection: some View {
@ -215,6 +236,20 @@ struct SettingsTabs: View {
} }
} }
private var editAccountButton: some View {
Button(role: isEditingAccount ? .none : .destructive) {
withAnimation {
isEditingAccount.toggle()
}
} label: {
if isEditingAccount {
Text("action.done")
} else {
Text("account.action.logout")
}
}
}
private var remoteLocalTimelinesView: some View { private var remoteLocalTimelinesView: some View {
Form { Form {
ForEach(preferences.remoteLocalTimelines, id: \.self) { server in ForEach(preferences.remoteLocalTimelines, id: \.self) { server in

View file

@ -6,12 +6,22 @@ import SwiftUI
@MainActor @MainActor
public class AppAccountViewModel: ObservableObject { public class AppAccountViewModel: ObservableObject {
private static var avatarsCache: [String: UIImage] = [:] private static var avatarsCache: [String: UIImage] = [:]
private static var accountsCache: [String: Account] = [:]
var appAccount: AppAccount var appAccount: AppAccount
let client: Client let client: Client
let isCompact: Bool let isCompact: Bool
@Published var account: Account? @Published var account: Account? {
didSet {
if let account {
refreshAcct(account: account)
Task {
await refreshAvatar(account: account)
}
}
}
}
@Published var roundedAvatar: UIImage? @Published var roundedAvatar: UIImage?
var acct: String { var acct: String {
@ -30,23 +40,31 @@ public class AppAccountViewModel: ObservableObject {
func fetchAccount() async { func fetchAccount() async {
do { do {
account = Self.accountsCache[appAccount.id]
roundedAvatar = Self.avatarsCache[appAccount.id]
account = try await client.get(endpoint: Accounts.verifyCredentials) account = try await client.get(endpoint: Accounts.verifyCredentials)
if appAccount.accountName == nil, let account {
Self.accountsCache[appAccount.id] = account
} catch {}
}
private func refreshAcct(account: Account) {
do {
if appAccount.accountName == nil {
appAccount.accountName = "\(account.acct)@\(appAccount.server)" appAccount.accountName = "\(account.acct)@\(appAccount.server)"
try appAccount.save() try appAccount.save()
} }
} catch { }
}
if let account { private func refreshAvatar(account: Account) async {
if let image = Self.avatarsCache[account.id] { if roundedAvatar == nil,
roundedAvatar = image let (data, _) = try? await URLSession.shared.data(from: account.avatar),
} else if let (data, _) = try? await URLSession.shared.data(from: account.avatar), let image = UIImage(data: data)?.roundedImage {
let image = UIImage(data: data)?.roundedImage
{
roundedAvatar = image roundedAvatar = image
Self.avatarsCache[account.id] = image Self.avatarsCache[account.id] = image
} }
} }
} catch {}
}
} }

View file

@ -54,7 +54,8 @@ public struct AppAccountsSelectorView: View {
if let avatar = currentAccount.account?.avatar, !currentAccount.isLoadingAccount { if let avatar = currentAccount.account?.avatar, !currentAccount.isLoadingAccount {
AvatarView(url: avatar, size: avatarSize) AvatarView(url: avatar, size: avatarSize)
} else { } else {
ProgressView() AvatarView(url: nil, size: avatarSize)
.redacted(reason: .placeholder)
} }
}.overlay(alignment: .topTrailing) { }.overlay(alignment: .topTrailing) {
if !currentAccount.followRequests.isEmpty { if !currentAccount.followRequests.isEmpty {

View file

@ -39,10 +39,10 @@ public struct AvatarView: View {
} }
} }
public let url: URL public let url: URL?
public let size: Size public let size: Size
public init(url: URL, size: Size = .status) { public init(url: URL?, size: Size = .status) {
self.url = url self.url = url
self.size = size self.size = size
} }

View file

@ -4,6 +4,8 @@ import Network
@MainActor @MainActor
public class CurrentAccount: ObservableObject { public class CurrentAccount: ObservableObject {
static private var accountsCache: [String: Account] = [:]
@Published public private(set) var account: Account? @Published public private(set) var account: Account?
@Published public private(set) var lists: [List] = [] @Published public private(set) var lists: [List] = []
@Published public private(set) var tags: [Tag] = [] @Published public private(set) var tags: [Tag] = []
@ -57,9 +59,13 @@ public class CurrentAccount: ObservableObject {
account = nil account = nil
return return
} }
account = Self.accountsCache[client.id]
if account == nil {
isLoadingAccount = true isLoadingAccount = true
}
account = try? await client.get(endpoint: Accounts.verifyCredentials) account = try? await client.get(endpoint: Accounts.verifyCredentials)
isLoadingAccount = false isLoadingAccount = false
Self.accountsCache[client.id] = account
} }
public func fetchLists() async { public func fetchLists() async {