forked from shadowfacts/Tusker
141 lines
4.2 KiB
Swift
141 lines
4.2 KiB
Swift
|
//
|
||
|
// SwitchAccountContainerView.swift
|
||
|
// ShareExtension
|
||
|
//
|
||
|
// Created by Shadowfacts on 4/19/23.
|
||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||
|
//
|
||
|
|
||
|
import SwiftUI
|
||
|
import UserAccounts
|
||
|
import TuskerPreferences
|
||
|
import Pachyderm
|
||
|
import Combine
|
||
|
import ComposeUI
|
||
|
|
||
|
struct SwitchAccountContainerView: View {
|
||
|
let content: AnyView
|
||
|
let mastodonContextPublisher: CurrentValueSubject<ShareMastodonContext, Never>
|
||
|
|
||
|
var accounts: [UserAccountInfo] {
|
||
|
UserAccountsManager.shared.accounts
|
||
|
}
|
||
|
|
||
|
var body: some View {
|
||
|
if accounts.count > 1 {
|
||
|
Menu {
|
||
|
ForEach(accounts) { account in
|
||
|
Button(action: { selectAccount(account) }) {
|
||
|
AccountButtonLabel(account: account)
|
||
|
}
|
||
|
}
|
||
|
} label: {
|
||
|
HStack(alignment: .center) {
|
||
|
VStack(spacing: 2) {
|
||
|
Image(systemName: "arrowtriangle.up.fill")
|
||
|
.resizable()
|
||
|
.frame(width: 10, height: 5)
|
||
|
Image(systemName: "arrowtriangle.down.fill")
|
||
|
.resizable()
|
||
|
.frame(width: 10, height: 5)
|
||
|
}
|
||
|
.foregroundColor(.secondary)
|
||
|
|
||
|
content
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
content
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private func selectAccount(_ account: UserAccountInfo) {
|
||
|
mastodonContextPublisher.send(ShareMastodonContext(accountInfo: account))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private struct AccountButtonLabel: View {
|
||
|
static let urlSession = URLSession(configuration: .ephemeral)
|
||
|
|
||
|
let account: UserAccountInfo
|
||
|
@State private var avatarImage: Image?
|
||
|
|
||
|
var body: some View {
|
||
|
label
|
||
|
.task {
|
||
|
await fetchAvatar()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@ViewBuilder
|
||
|
private var label: some View {
|
||
|
// subtitles only started being supported on 16.4
|
||
|
if #available(iOS 16.4, *) {
|
||
|
Label {
|
||
|
Text(account.username)
|
||
|
} icon: {
|
||
|
avatar
|
||
|
}
|
||
|
Text(account.instanceURL.host!)
|
||
|
} else {
|
||
|
Label {
|
||
|
Text("@\(account.username)@\(account.instanceURL.host!)")
|
||
|
} icon: {
|
||
|
avatar
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@ViewBuilder
|
||
|
private var avatar: some View {
|
||
|
if let avatarImage {
|
||
|
avatarImage
|
||
|
} else {
|
||
|
avatarPlaceholder
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private var avatarPlaceholder: Image {
|
||
|
switch Preferences.shared.avatarStyle {
|
||
|
case .circle:
|
||
|
return Image(systemName: "person.crop.circle")
|
||
|
case .roundRect:
|
||
|
return Image(systemName: "person.crop.square")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private func fetchAvatar() async {
|
||
|
let client = Client(baseURL: account.instanceURL, accessToken: account.accessToken, session: Self.urlSession)
|
||
|
let account: Account? = await withCheckedContinuation({ continuation in
|
||
|
client.run(Client.getSelfAccount()) { response in
|
||
|
switch response {
|
||
|
case .success(let account, _):
|
||
|
continuation.resume(returning: account)
|
||
|
case .failure(_):
|
||
|
continuation.resume(returning: nil)
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
if let account,
|
||
|
let avatarURL = account.avatar,
|
||
|
let data = try? await Self.urlSession.data(from: avatarURL).0,
|
||
|
let image = UIImage(data: data) {
|
||
|
let size = CGSize(width: 50, height: 50)
|
||
|
let renderer = UIGraphicsImageRenderer(size: size)
|
||
|
let clipped = renderer.image { context in
|
||
|
let bounds = CGRect(origin: .zero, size: size)
|
||
|
let path: UIBezierPath
|
||
|
switch Preferences.shared.avatarStyle {
|
||
|
case .circle:
|
||
|
path = UIBezierPath(ovalIn: bounds)
|
||
|
case .roundRect:
|
||
|
path = UIBezierPath(roundedRect: bounds, cornerRadius: 5)
|
||
|
}
|
||
|
path.addClip()
|
||
|
image.draw(in: bounds)
|
||
|
}
|
||
|
self.avatarImage = Image(uiImage: clipped)
|
||
|
}
|
||
|
}
|
||
|
}
|