~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-ios

1 collaborator
0
1
main
70 lines · 2.0 KB Raw History
import NukeUI
import SwiftUI

struct AvatarView: View {
    let url: String?
    var size: CGFloat = 32
    /// Set to false to suppress all NukeUI transitions — use when the avatar is inside
    /// an animated parent (e.g. story parallax pane) so it snaps atomically.
    var animated: Bool = true

    private var imageURL: URL? {
        guard let url else { return nil }
        return URL(string: url)
    }

    var body: some View {
        LazyImage(url: imageURL) { state in
            if let image = state.image {
                image
                    .resizable()
            } else {
                fallback
            }
        }
        .animation(animated ? .default : nil, value: imageURL)
        .frame(width: size, height: size)
        .clipShape(Circle())
    }

    private var fallback: some View {
        ZStack {
            Circle().fill(Color(.systemGray4))
            Image(systemName: "person.fill")
                .font(.system(size: size * 0.45))
                .foregroundStyle(Color(.systemGray2))
                .accessibilityHidden(true)
        }
    }
}

#Preview {
    VStack(spacing: 24) {
        VStack(spacing: 8) {
            Text("Fallback (nil URL)")
                .font(.caption)
                .foregroundStyle(.secondary)
            HStack(spacing: 20) {
                AvatarView(url: nil, size: 32)
                AvatarView(url: nil, size: 48)
                AvatarView(url: nil, size: 80)
            }
        }

        Divider()

        VStack(spacing: 8) {
            Text("Bad URL (load failure fallback)")
                .font(.caption)
                .foregroundStyle(.secondary)
            HStack(spacing: 20) {
                AvatarView(url: "https://invalid.example/avatar.jpg", size: 32)
                AvatarView(url: "https://invalid.example/avatar.jpg", size: 48)
                AvatarView(url: "https://invalid.example/avatar.jpg", size: 80)
            }
        }
    }
    .padding()
    .background(Color(.systemBackground))
    .grainPreview()
}