IceCubesApp/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift

153 lines
4.4 KiB
Swift
Raw Normal View History

2023-12-18 07:22:59 +00:00
import Models
2023-02-19 14:29:07 +00:00
import Nuke
2023-02-21 06:23:42 +00:00
import NukeUI
2023-01-17 10:36:01 +00:00
import SwiftUI
2022-12-19 11:28:55 +00:00
@MainActor
2022-12-19 11:28:55 +00:00
public struct AvatarView: View {
2023-09-18 19:03:52 +00:00
@Environment(Theme.self) private var theme
public let avatar: URL?
public let config: FrameConfig
public var body: some View {
if let avatar {
AvatarImage(avatar, config: adaptiveConfig)
.frame(width: config.width, height: config.height)
} else {
AvatarPlaceHolder(config: adaptiveConfig)
}
}
2023-01-17 10:36:01 +00:00
private var adaptiveConfig: FrameConfig {
let cornerRadius: CGFloat = if config == .badge || theme.avatarShape == .circle {
2023-12-18 07:22:59 +00:00
config.width / 2
} else {
2023-12-18 07:22:59 +00:00
config.cornerRadius
}
return FrameConfig(width: config.width, height: config.height, cornerRadius: cornerRadius)
}
2023-01-17 10:36:01 +00:00
public init(_ avatar: URL? = nil, config: FrameConfig = .status) {
self.avatar = avatar
self.config = config
}
2023-01-17 10:36:01 +00:00
2024-03-11 07:59:29 +00:00
@MainActor
public struct FrameConfig: Equatable, Sendable {
public let size: CGSize
public var width: CGFloat { size.width }
public var height: CGFloat { size.height }
let cornerRadius: CGFloat
init(width: CGFloat, height: CGFloat, cornerRadius: CGFloat = 4) {
2023-12-18 07:22:59 +00:00
size = CGSize(width: width, height: height)
self.cornerRadius = cornerRadius
}
public static let account = FrameConfig(width: 80, height: 80)
2023-12-18 07:22:59 +00:00
#if targetEnvironment(macCatalyst)
public static let status = FrameConfig(width: 48, height: 48)
#else
public static let status = FrameConfig(width: 40, height: 40)
#endif
public static let embed = FrameConfig(width: 34, height: 34)
public static let badge = FrameConfig(width: 28, height: 28, cornerRadius: 14)
public static let list = FrameConfig(width: 20, height: 20, cornerRadius: 10)
public static let boost = FrameConfig(width: 12, height: 12, cornerRadius: 6)
2022-12-19 11:28:55 +00:00
}
}
2023-01-17 10:36:01 +00:00
struct AvatarView_Previews: PreviewProvider {
static var previews: some View {
PreviewWrapper()
.padding()
.previewLayout(.sizeThatFits)
}
}
struct PreviewWrapper: View {
@State private var isCircleAvatar = false
var body: some View {
VStack(alignment: .leading) {
AvatarView(Self.account.avatar)
.environment(Theme.shared)
Toggle("Avatar Shape", isOn: $isCircleAvatar)
}
.onChange(of: isCircleAvatar) {
2023-12-18 07:22:59 +00:00
Theme.shared.avatarShape = isCircleAvatar ? .circle : .rounded
}
.onAppear {
2023-12-18 07:22:59 +00:00
Theme.shared.avatarShape = isCircleAvatar ? .circle : .rounded
}
}
private static let account = Account(
id: UUID().uuidString,
username: "@clattner_llvm",
displayName: "Chris Lattner",
avatar: URL(string: "https://pbs.twimg.com/profile_images/1484209565788897285/1n6Viahb_400x400.jpg")!,
header: URL(string: "https://pbs.twimg.com/profile_banners/2543588034/1656822255/1500x500")!,
acct: "clattner_llvm@example.com",
note: .init(stringValue: "Building beautiful things @Modular_AI 🔥, lifting the world of production AI/ML software into a new phase of innovation. Were hiring! 🚀🧠"),
createdAt: ServerDate(),
followersCount: 77100,
followingCount: 167,
statusesCount: 123,
lastStatusAt: nil,
fields: [],
locked: false,
emojis: [],
url: URL(string: "https://nondot.org/sabre/")!,
source: nil,
bot: false,
2023-12-18 07:22:59 +00:00
discoverable: true
)
}
struct AvatarImage: View {
@Environment(\.redactionReasons) private var reasons
public let avatar: URL
public let config: AvatarView.FrameConfig
var body: some View {
if reasons == .placeholder {
AvatarPlaceHolder(config: config)
} else {
LazyImage(request: ImageRequest(url: avatar, processors: [.resize(size: config.size)])
) { state in
if let image = state.image {
2023-11-20 17:43:16 +00:00
image
.resizable()
.scaledToFit()
.clipShape(RoundedRectangle(cornerRadius: config.cornerRadius))
.overlay(
RoundedRectangle(cornerRadius: config.cornerRadius)
.stroke(.primary.opacity(0.25), lineWidth: 1)
)
2024-01-23 07:13:45 +00:00
} else {
RoundedRectangle(cornerRadius: config.cornerRadius)
.stroke(.primary.opacity(0.25), lineWidth: 1)
2022-12-19 11:28:55 +00:00
}
2023-01-17 10:36:01 +00:00
}
}
}
init(_ avatar: URL, config: AvatarView.FrameConfig) {
self.avatar = avatar
self.config = config
}
}
struct AvatarPlaceHolder: View {
let config: AvatarView.FrameConfig
var body: some View {
RoundedRectangle(cornerRadius: config.cornerRadius)
.fill(.gray)
.frame(width: config.width, height: config.height)
2022-12-19 11:28:55 +00:00
}
}