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
2023-09-18 05:01:23 +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
2023-01-04 16:48:02 +00:00
2023-11-27 08:00:52 +00:00
public let avatar : URL ?
2023-11-20 09:59:49 +00:00
public let config : FrameConfig
public var body : some View {
2023-11-27 08:00:52 +00:00
if let avatar {
AvatarImage ( avatar , config : adaptiveConfig )
. frame ( width : config . width , height : config . height )
2023-11-20 09:59:49 +00:00
} else {
AvatarPlaceHolder ( config : adaptiveConfig )
2022-12-22 11:26:11 +00:00
}
2023-11-20 09:59:49 +00:00
}
2023-01-17 10:36:01 +00:00
2023-11-20 09:59:49 +00:00
private var adaptiveConfig : FrameConfig {
2024-01-06 10:21:07 +00:00
let cornerRadius : CGFloat = if config = = . badge || theme . avatarShape = = . circle {
2023-12-18 07:22:59 +00:00
config . width / 2
2023-11-20 09:59:49 +00:00
} else {
2023-12-18 07:22:59 +00:00
config . cornerRadius
2022-12-23 09:41:55 +00:00
}
2023-11-20 09:59:49 +00:00
return FrameConfig ( width : config . width , height : config . height , cornerRadius : cornerRadius )
2022-12-22 11:26:11 +00:00
}
2023-01-17 10:36:01 +00:00
2023-11-27 08:00:52 +00:00
public init ( _ avatar : URL ? = nil , config : FrameConfig = . status ) {
self . avatar = avatar
2023-11-20 09:59:49 +00:00
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 {
2023-11-20 09:59:49 +00:00
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 )
2023-11-20 09:59:49 +00:00
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
2023-11-20 09:59:49 +00:00
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-11-20 09:59:49 +00:00
}
2023-01-17 10:36:01 +00:00
2023-11-20 09:59:49 +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 ) {
2023-11-27 08:00:52 +00:00
AvatarView ( Self . account . avatar )
2023-11-20 09:59:49 +00:00
. 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
2023-11-20 09:59:49 +00:00
}
. onAppear {
2023-12-18 07:22:59 +00:00
Theme . shared . avatarShape = isCircleAvatar ? . circle : . rounded
2023-11-20 09:59:49 +00:00
}
}
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. We’ re 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
)
2023-11-20 09:59:49 +00:00
}
struct AvatarImage : View {
@ Environment ( \ . redactionReasons ) private var reasons
2023-11-27 08:00:52 +00:00
public let avatar : URL
2023-11-20 09:59:49 +00:00
public let config : AvatarView . FrameConfig
var body : some View {
if reasons = = . placeholder {
AvatarPlaceHolder ( config : config )
} else {
2023-11-27 08:00:52 +00:00
LazyImage ( request : ImageRequest ( url : avatar , processors : [ . resize ( size : config . size ) ] )
2023-11-20 09:59:49 +00:00
) { state in
if let image = state . image {
2023-11-20 17:43:16 +00:00
image
. resizable ( )
. scaledToFit ( )
2023-11-20 09:59:49 +00:00
. 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
}
2023-01-04 16:48:02 +00:00
}
}
2023-11-27 08:00:52 +00:00
init ( _ avatar : URL , config : AvatarView . FrameConfig ) {
self . avatar = avatar
self . config = config
}
2023-11-20 09:59:49 +00:00
}
2023-01-04 16:48:02 +00:00
2023-11-20 09:59:49 +00:00
struct AvatarPlaceHolder : View {
let config : AvatarView . FrameConfig
2023-02-19 17:34:16 +00:00
2023-11-20 09:59:49 +00:00
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
}
2023-02-19 17:34:16 +00:00
}