mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-29 19:51:08 +00:00
All new accounts selector
This commit is contained in:
parent
5c69aa64bc
commit
15b704c97a
5 changed files with 98 additions and 120 deletions
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue