All new accounts selector

This commit is contained in:
Thomas Ricouard 2023-03-08 19:02:31 +01:00
parent 5c69aa64bc
commit 15b704c97a
5 changed files with 98 additions and 120 deletions

View File

@ -328,6 +328,12 @@ public struct AccountDetailView: View {
} label: {
Label("settings.push.navigation-title", systemImage: "bell")
}
Button {
routerPath.presentedSheet = .settings
} label: {
Label("settings.title", systemImage: "gear")
}
}
} label: {
Image(systemName: "ellipsis.circle")

View File

@ -5,7 +5,9 @@ import SwiftUI
public struct AppAccountView: View {
@EnvironmentObject private var routerPath: RouterPath
@EnvironmentObject var appAccounts: AppAccountsManager
@EnvironmentObject private var appAccounts: AppAccountsManager
@EnvironmentObject private var preferences: UserPreferences
@StateObject var viewModel: AppAccountViewModel
public init(viewModel: AppAccountViewModel) {
@ -47,6 +49,19 @@ public struct AppAccountView: View {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.white, .green)
.offset(x: 5, y: -5)
} else if viewModel.showBadge,
let token = viewModel.appAccount.oauthToken,
let notificationsCount = preferences.getNotificationsCount(for: token),
notificationsCount > 0{
ZStack {
Circle()
.fill(.red)
Text(notificationsCount > 99 ? "99+" : String(notificationsCount))
.foregroundColor(.white)
.font(.system(size: 9))
}
.frame(width: 20, height: 20)
.offset(x: 5, y: -5)
}
}
} else {
@ -66,9 +81,11 @@ public struct AppAccountView: View {
.foregroundColor(.gray)
}
}
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(.gray)
if viewModel.isInNavigation {
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(.gray)
}
}
.contentShape(Rectangle())
.onTapGesture {

View File

@ -12,6 +12,8 @@ public class AppAccountViewModel: ObservableObject {
var appAccount: AppAccount
let client: Client
let isCompact: Bool
let isInNavigation: Bool
let showBadge: Bool
@Published var account: Account? {
didSet {
@ -21,8 +23,6 @@ public class AppAccountViewModel: ObservableObject {
}
}
@Published var roundedAvatar: UIImage?
var acct: String {
if let acct = appAccount.accountName {
return acct
@ -31,24 +31,20 @@ public class AppAccountViewModel: ObservableObject {
}
}
public init(appAccount: AppAccount, isCompact: Bool = false) {
public init(appAccount: AppAccount, isCompact: Bool = false, isInNavigation: Bool = true, showBadge: Bool = false) {
self.appAccount = appAccount
self.isCompact = isCompact
self.isInNavigation = isInNavigation
self.showBadge = showBadge
client = .init(server: appAccount.server, oauthToken: appAccount.oauthToken)
}
func fetchAccount() async {
do {
account = Self.accountsCache[appAccount.id]
roundedAvatar = Self.avatarsCache[appAccount.id]
account = try await client.get(endpoint: Accounts.verifyCredentials)
Self.accountsCache[appAccount.id] = account
if let account {
await refreshAvatar(account: account)
}
} catch {}
}
@ -60,18 +56,4 @@ public class AppAccountViewModel: ObservableObject {
}
} catch {}
}
private func refreshAvatar(account: Account) async {
// Warning: Non-sendable type '(any URLSessionTaskDelegate)?' exiting main actor-isolated
// context in call to non-isolated instance method 'data(for:delegate:)' cannot cross actor
// boundary.
// This is on the defaulted-to-nil second parameter of `.data(from:delegate:)`.
// There is a Radar tracking this & others like it.
if 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

@ -6,10 +6,12 @@ public struct AppAccountsSelectorView: View {
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var appAccounts: AppAccountsManager
@EnvironmentObject private var theme: Theme
@ObservedObject var routerPath: RouterPath
@State private var accountsViewModel: [AppAccountViewModel] = []
@State private var isPresented: Bool = false
private let accountCreationEnabled: Bool
private let avatarSize: AvatarView.Size
@ -32,26 +34,18 @@ public struct AppAccountsSelectorView: View {
}
public var body: some View {
Group {
if UIDevice.current.userInterfaceIdiom == .pad {
labelView
.contextMenu {
menuView
}
} else {
Menu {
menuView
} label: {
labelView
}
}
}
.onTapGesture {
Button {
isPresented.toggle()
HapticManager.shared.fireHaptic(of: .buttonPress)
} label: {
labelView
}
.onAppear {
refreshAccounts()
}
.sheet(isPresented: $isPresented, content: {
accountsView.presentationDetents([.medium])
.onAppear {
refreshAccounts()
}
})
.onChange(of: currentAccount.account?.id) { _ in
refreshAccounts()
}
@ -67,7 +61,7 @@ public struct AppAccountsSelectorView: View {
.redacted(reason: .placeholder)
}
}.overlay(alignment: .topTrailing) {
if !currentAccount.followRequests.isEmpty || showNotificationBadge {
if (!currentAccount.followRequests.isEmpty || showNotificationBadge) && accountCreationEnabled {
Circle()
.fill(Color.red)
.frame(width: 9, height: 9)
@ -76,77 +70,71 @@ public struct AppAccountsSelectorView: View {
.accessibilityLabel("accessibility.app-account.selector.accounts")
}
@ViewBuilder
private var menuView: some View {
ForEach(accountsViewModel.sorted { $0.acct < $1.acct }, id: \.appAccount.id) { viewModel in
Section(viewModel.acct) {
Button {
if let account = currentAccount.account,
viewModel.account?.id == account.id
{
routerPath.navigate(to: .accountDetailWithAccount(account: account))
} else {
var transation = Transaction()
transation.disablesAnimations = true
withTransaction(transation) {
appAccounts.currentAccount = viewModel.appAccount
}
private var accountsView: some View {
NavigationStack {
List {
Section {
ForEach(accountsViewModel.sorted { $0.acct < $1.acct }, id: \.appAccount.id) { viewModel in
AppAccountView(viewModel: viewModel)
}
HapticManager.shared.fireHaptic(of: .buttonPress)
} label: {
HStack {
if let image = viewModel.roundedAvatar {
Image(uiImage: image)
}
let name = viewModel.account.flatMap { account in
account.displayName?.isEmpty != false ? "@\(account.acct)" : account.displayName
} ?? ""
if let token = viewModel.appAccount.oauthToken,
preferences.getNotificationsCount(for: token) > 0
{
Text("\(name) (\(preferences.getNotificationsCount(for: token)))")
} else {
Text("\(name)")
}
.listRowBackground(theme.primaryBackgroundColor)
if accountCreationEnabled {
Section {
Button {
isPresented = false
HapticManager.shared.fireHaptic(of: .buttonPress)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
routerPath.presentedSheet = .addAccount
}
} label: {
Label("app-account.button.add", systemImage: "person.badge.plus")
}
settingsButton
}
.listRowBackground(theme.primaryBackgroundColor)
}
}
.listStyle(.insetGrouped)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.navigationTitle("settings.section.accounts")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
isPresented.toggle()
} label: {
Image(systemName: "xmark.circle")
}
}
if accountCreationEnabled {
ToolbarItem(placement: .navigationBarTrailing) {
settingsButton
}
}
}
}
if accountCreationEnabled {
Divider()
Button {
HapticManager.shared.fireHaptic(of: .buttonPress)
routerPath.presentedSheet = .addAccount
} label: {
Label("app-account.button.add", systemImage: "person.badge.plus")
}
}
if UIDevice.current.userInterfaceIdiom == .phone && accountCreationEnabled {
Divider()
Button {
HapticManager.shared.fireHaptic(of: .buttonPress)
}
private var settingsButton: some View {
Button {
isPresented = false
HapticManager.shared.fireHaptic(of: .buttonPress)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
routerPath.presentedSheet = .settings
} label: {
Label("tab.settings", systemImage: "gear")
}
} label: {
Label("tab.settings", systemImage: "gear")
}
}
private func refreshAccounts() {
if accountsViewModel.isEmpty || appAccounts.availableAccounts.count != accountsViewModel.count {
accountsViewModel = []
for account in appAccounts.availableAccounts {
let viewModel: AppAccountViewModel = .init(appAccount: account)
Task {
await viewModel.fetchAccount()
if !accountsViewModel.contains(where: { $0.acct == viewModel.acct }) {
accountsViewModel.append(viewModel)
}
}
}
accountsViewModel = []
for account in appAccounts.availableAccounts {
let viewModel: AppAccountViewModel = .init(appAccount: account, isInNavigation: false, showBadge: true)
accountsViewModel.append(viewModel)
}
}
}

View File

@ -1,15 +0,0 @@
import UIKit
public extension UIImage {
var roundedImage: UIImage? {
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
UIGraphicsBeginImageContextWithOptions(size, false, 1)
defer { UIGraphicsEndImageContext() }
UIBezierPath(
roundedRect: rect,
cornerRadius: size.height
).addClip()
draw(in: rect)
return UIGraphicsGetImageFromCurrentImageContext()
}
}