IceCubesApp/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift

194 lines
5.5 KiB
Swift
Raw Normal View History

import DesignSystem
2023-01-17 10:36:01 +00:00
import Env
import SwiftUI
2023-09-19 07:18:20 +00:00
@MainActor
public struct AppAccountsSelectorView: View {
2023-09-19 07:18:20 +00:00
@Environment(UserPreferences.self) private var preferences
@Environment(CurrentAccount.self) private var currentAccount
@Environment(AppAccountsManager.self) private var appAccounts
2023-09-18 19:03:52 +00:00
@Environment(Theme.self) private var theme
2023-01-17 10:36:01 +00:00
var routerPath: RouterPath
2023-01-17 10:36:01 +00:00
@State private var accountsViewModel: [AppAccountViewModel] = []
2023-03-08 18:02:31 +00:00
@State private var isPresented: Bool = false
2023-01-16 12:39:35 +00:00
private let accountCreationEnabled: Bool
private let avatarConfig: AvatarView.FrameConfig
2023-02-26 05:45:57 +00:00
private var showNotificationBadge: Bool {
accountsViewModel
2023-02-26 05:45:57 +00:00
.filter { $0.account?.id != currentAccount.account?.id }
2023-09-16 12:15:03 +00:00
.compactMap(\.appAccount.oauthToken)
2023-09-19 06:44:11 +00:00
.map { preferences.notificationsCount[$0] ?? 0 }
2023-02-26 05:45:57 +00:00
.reduce(0, +) > 0
}
2023-03-13 12:38:28 +00:00
private var preferredHeight: CGFloat {
var baseHeight: CGFloat = 310
baseHeight += CGFloat(60 * accountsViewModel.count)
return baseHeight
}
2023-01-17 10:36:01 +00:00
public init(routerPath: RouterPath,
2023-01-16 12:39:35 +00:00
accountCreationEnabled: Bool = true,
avatarConfig: AvatarView.FrameConfig = .badge)
2023-01-17 10:36:01 +00:00
{
self.routerPath = routerPath
2023-01-16 12:39:35 +00:00
self.accountCreationEnabled = accountCreationEnabled
self.avatarConfig = avatarConfig
}
2023-01-17 10:36:01 +00:00
public var body: some View {
2023-03-08 18:02:31 +00:00
Button {
isPresented.toggle()
HapticManager.shared.fireHaptic(.buttonPress)
2023-03-08 18:02:31 +00:00
} label: {
labelView
2023-11-20 17:43:16 +00:00
.contentShape(Rectangle())
}
2023-03-08 18:02:31 +00:00
.sheet(isPresented: $isPresented, content: {
2023-10-16 17:08:59 +00:00
accountsView.presentationDetents([.height(preferredHeight), .large])
.presentationBackground(.thinMaterial)
.presentationCornerRadius(16)
.onAppear {
refreshAccounts()
}
2023-03-08 18:02:31 +00:00
})
.onChange(of: currentAccount.account?.id) {
refreshAccounts()
}
.onAppear {
refreshAccounts()
}
Timeline & Timeline detail accessibility uplift (#1323) * Improve accessibility of StatusPollView Previously, this view did not provide the proper context to indicate that it represented a poll. Now, we’ve added - A container that will stay “Active poll” or “Poll results” when the cursor first hits one of the options; - A prefix to say “Option X of Y” before each option; - A Selected trait on the selected option(s), if present - Consolidating and adding an `.updatesFrequently` trait to the footer view with the countdown. * Add poll description in StatusRowView combinedAccessibilityLabel This largely duplicates the logic in `StatusPollView`. * Improve accessibility of media attachments Previously, the media attachments without alt text would not show up in the consolidated `StatusRowView`, nor would they be meaningfully explained on the status detail screen. Now, they are presented with their attachment type. * Change accessibilityRepresentation of AppAcountsSelectorView * Change Notifications tab title view accessibility representation to Menu Previously it would present as a button * Hide layout `Rectangle`s from accessibility * Consolidate `StatusRowDetailView` accessibility representation * Improve readability of Poll accessibility label * Ensure poll options don’t present as interactive when the poll is finished * Improve accessibility of StatusRowCardView Previously, it would present as four separate elements, including an image without a description, all interactive, none with an interactive trait. Now, it presents as a single element with the `.link` trait * Improve accessibility of StatusRowHeaderView Previously, it had no traits and no actions except inherited ones. Now it presents as a button, triggering its primary action. It also has custom actions corresponding to its context menu * Avoid applying the StatusRowView custom actions to every view when contained * Provide context for the application name * Add pauses to StatusRowView combinedAccessibilityLabel * Hide `TimelineView.scrollToTopView` from accessibility * Set appropriate font style on Notification header After the change the Text needed a `.headline` style to match the prior appearance. * Fix bug in accessibilityRepresentation of TimelineView nav bar title Previously, it would not display the proper label for .remoteLocal filter options. * Ensure that pop-up button nav bar titles are interactive * Ensure TextView responds to Environment.sizeCategory This resolves #1309 * Fix button --------- Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
2023-03-28 16:48:58 +00:00
.accessibilityRepresentation {
Menu("accessibility.app-account.selector.accounts") {}
.accessibilityHint("accessibility.app-account.selector.accounts.hint")
.accessibilityRemoveTraits(.isButton)
}
}
2023-01-17 10:36:01 +00:00
2023-01-16 20:15:33 +00:00
@ViewBuilder
private var labelView: some View {
Group {
if let account = currentAccount.account, !currentAccount.isLoadingAccount {
AvatarView(account.avatar, config: avatarConfig)
} else {
AvatarView(config: avatarConfig)
2023-11-20 17:43:16 +00:00
.redacted(reason: .placeholder)
.allowsHitTesting(false)
}
}.overlay(alignment: .topTrailing) {
2023-09-16 12:15:03 +00:00
if !currentAccount.followRequests.isEmpty || showNotificationBadge, accountCreationEnabled {
Circle()
.fill(Color.red)
.frame(width: 9, height: 9)
}
2023-01-16 20:15:33 +00:00
}
}
2023-01-17 10:36:01 +00:00
2023-03-08 18:02:31 +00:00
private var accountsView: some View {
NavigationStack {
List {
Section {
ForEach(accountsViewModel.sorted { $0.acct < $1.acct }, id: \.appAccount.id) { viewModel in
AppAccountView(viewModel: viewModel, isParentPresented: $isPresented)
2023-01-16 20:15:33 +00:00
}
addAccountButton
2024-02-11 17:59:34 +00:00
#if os(visionOS)
2024-02-14 11:48:14 +00:00
.foregroundStyle(theme.labelColor)
2024-02-11 17:59:34 +00:00
#endif
2023-03-08 18:02:31 +00:00
}
#if !os(visionOS)
2024-02-14 11:48:14 +00:00
.listRowBackground(theme.primaryBackgroundColor)
#endif
2023-03-13 12:38:28 +00:00
2023-03-08 18:02:31 +00:00
if accountCreationEnabled {
Section {
settingsButton
aboutButton
supportButton
2023-01-16 20:15:33 +00:00
}
2024-02-11 17:59:34 +00:00
#if os(visionOS)
.foregroundStyle(theme.labelColor)
#else
.listRowBackground(theme.primaryBackgroundColor)
#endif
2023-01-16 20:15:33 +00:00
}
}
2023-03-08 18:02:31 +00:00
.listStyle(.insetGrouped)
.scrollContentBackground(.hidden)
2023-12-06 17:56:19 +00:00
.background(.clear)
2023-03-08 18:02:31 +00:00
.navigationTitle("settings.section.accounts")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
2023-03-08 18:02:31 +00:00
Button {
isPresented.toggle()
} label: {
Text("action.done").bold()
2023-03-08 18:02:31 +00:00
}
}
2023-01-16 20:15:33 +00:00
}
2023-09-22 06:35:21 +00:00
.environment(routerPath)
2023-01-16 20:15:33 +00:00
}
2023-03-08 18:02:31 +00:00
}
2024-02-14 11:48:14 +00:00
private var addAccountButton: some View {
Button {
isPresented = false
HapticManager.shared.fireHaptic(.buttonPress)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
routerPath.presentedSheet = .addAccount
}
} label: {
Label("app-account.button.add", systemImage: "person.badge.plus")
}
}
2023-03-13 12:38:28 +00:00
2023-03-08 18:02:31 +00:00
private var settingsButton: some View {
Button {
isPresented = false
HapticManager.shared.fireHaptic(.buttonPress)
2023-03-08 18:02:31 +00:00
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
routerPath.presentedSheet = .settings
}
2023-03-08 18:02:31 +00:00
} label: {
Label("tab.settings", systemImage: "gear")
}
2023-01-16 20:15:33 +00:00
}
2024-02-14 11:48:14 +00:00
private var supportButton: some View {
Button {
isPresented = false
HapticManager.shared.fireHaptic(.buttonPress)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
routerPath.presentedSheet = .support
}
} label: {
Label("settings.app.support", systemImage: "wand.and.stars")
}
}
2024-02-14 11:48:14 +00:00
private var aboutButton: some View {
Button {
isPresented = false
HapticManager.shared.fireHaptic(.buttonPress)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
routerPath.presentedSheet = .about
}
} label: {
Label("settings.app.about", systemImage: "info.circle")
}
}
2023-01-17 10:36:01 +00:00
private func refreshAccounts() {
2023-03-08 18:02:31 +00:00
accountsViewModel = []
for account in appAccounts.availableAccounts {
let viewModel: AppAccountViewModel = .init(appAccount: account, isInSettings: false, showBadge: true)
2023-03-08 18:02:31 +00:00
accountsViewModel.append(viewModel)
}
}
}