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()
@State private var addAccountSheetPresented = false
@State private var isEditingAccount = false
@Binding var popToRootTab: Tab
@ -69,27 +70,47 @@ struct SettingsTabs: View {
private var accountsSection: some View {
Section("settings.section.accounts") {
ForEach(appAccountsManager.availableAccounts) { account in
AppAccountView(viewModel: .init(appAccount: account))
HStack {
if isEditingAccount {
Button {
Task {
await logoutAccount(account: account)
}
} label: {
Image(systemName: "trash")
.renderingMode(.template)
.tint(.red)
}
}
AppAccountView(viewModel: .init(appAccount: account))
}
}
.onDelete { indexSet in
if let index = indexSet.first {
let account = appAccountsManager.availableAccounts[index]
if let token = account.oauthToken,
let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token })
{
Task {
let client = Client(server: account.server, oauthToken: token)
await TimelineCache.shared.clearCache(for: client)
await sub.deleteSubscription()
appAccountsManager.delete(account: account)
}
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,
let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token })
{
let client = Client(server: account.server, oauthToken: token)
await TimelineCache.shared.clearCache(for: client)
await sub.deleteSubscription()
appAccountsManager.delete(account: account)
}
}
@ViewBuilder
private var generalSection: some View {
@ -214,6 +235,20 @@ struct SettingsTabs: View {
AddAccountView()
}
}
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 {
Form {

View file

@ -6,12 +6,22 @@ import SwiftUI
@MainActor
public class AppAccountViewModel: ObservableObject {
private static var avatarsCache: [String: UIImage] = [:]
private static var accountsCache: [String: Account] = [:]
var appAccount: AppAccount
let client: Client
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?
var acct: String {
@ -30,23 +40,31 @@ public class AppAccountViewModel: ObservableObject {
func fetchAccount() async {
do {
account = Self.accountsCache[appAccount.id]
roundedAvatar = Self.avatarsCache[appAccount.id]
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)"
try appAccount.save()
}
if let account {
if let image = Self.avatarsCache[account.id] {
roundedAvatar = image
} else if let (data, _) = try? await URLSession.shared.data(from: account.avatar),
let image = UIImage(data: data)?.roundedImage
{
roundedAvatar = image
Self.avatarsCache[account.id] = image
}
}
} catch {}
} catch { }
}
private func refreshAvatar(account: Account) async {
if roundedAvatar == nil,
let (data, _) = try? await URLSession.shared.data(from: account.avatar),
let image = UIImage(data: data)?.roundedImage {
roundedAvatar = image
Self.avatarsCache[account.id] = image
}
}
}

View file

@ -54,7 +54,8 @@ public struct AppAccountsSelectorView: View {
if let avatar = currentAccount.account?.avatar, !currentAccount.isLoadingAccount {
AvatarView(url: avatar, size: avatarSize)
} else {
ProgressView()
AvatarView(url: nil, size: avatarSize)
.redacted(reason: .placeholder)
}
}.overlay(alignment: .topTrailing) {
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 init(url: URL, size: Size = .status) {
public init(url: URL?, size: Size = .status) {
self.url = url
self.size = size
}

View file

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