import SwiftUI final class HAProgressViewModel: ObservableObject { @Published var isAnimating = false @Published var rotationDegrees: CGFloat = 0 @Published var trimEnd: CGFloat = 0.1 } public struct HAProgressView: View { public enum Style { case small case medium case large case extraLarge var size: CGSize { switch self { case .small: return CGSize(width: 24, height: 24) case .medium: return CGSize(width: 28, height: 28) case .large: return CGSize(width: 48, height: 48) case .extraLarge: return CGSize(width: 68, height: 68) } } var lineWidth: CGFloat { 4 } } public enum ColorType { /// When display in light background case `default` /// When displayed on accented background such as haPrimary case light } @StateObject private var viewModel = HAProgressViewModel() let style: Style let colorType: ColorType public init(style: Style = .medium, colorType: ColorType = .default) { self.style = style self.colorType = colorType } public var body: some View { ZStack { Circle() .stroke( colorType == .default ? Color.track : Color.white, style: StrokeStyle(lineWidth: style.lineWidth) ) .frame(width: style.size.width, height: style.size.height) Circle() .trim(from: 0, to: viewModel.trimEnd) .stroke( Color.haPrimary.opacity(colorType == .default ? 1 : 0.5), style: StrokeStyle(lineWidth: style.lineWidth, lineCap: .round) ) .frame(width: style.size.width, height: style.size.height) .rotationEffect(Angle(degrees: viewModel.rotationDegrees)) .onAppear { guard !viewModel.isAnimating else { return } viewModel.isAnimating = true withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) { viewModel.rotationDegrees = 360 } withAnimation(.easeInOut(duration: 1.75).repeatForever(autoreverses: true)) { viewModel.trimEnd = 0.7 } } } .frame(width: style.size.width, height: style.size.height, alignment: .center) } } #Preview("In HStack") { HStack(spacing: DesignSystem.Spaces.two) { HAProgressView(style: .small) HAProgressView(style: .medium) HAProgressView(style: .large) HAProgressView(style: .extraLarge) } } #Preview("In List") { List { HAProgressView(style: .small) HAProgressView(style: .medium) HAProgressView(style: .large) HAProgressView(style: .extraLarge) } } #Preview("In VStack") { VStack { HAProgressView(style: .small) HAProgressView(style: .medium) HAProgressView(style: .large) HAProgressView(style: .extraLarge) } } #Preview("In Navigation view small") { NavigationView { HAProgressView(style: .small) } } #Preview("In Navigation view medium") { NavigationView { HAProgressView(style: .medium) } } #Preview("In Navigation view large") { NavigationView { HAProgressView(style: .large) } } #Preview("In Navigation view extra lar") { NavigationView { HAProgressView(style: .extraLarge) } }