Files
iOS/Sources/Shared/DesignSystem/Components/HAProgressView.swift
Bruno Pantaleão Gonçalves 6cdba80ca1 Fix HAProgressView animation positioning (#4403)
<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->
- HAProgressView had a glitch when positioned inside a NavigationView,
where it would animate incorrectly, this PR fixes it

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->

## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->
2026-03-03 14:34:35 +01:00

131 lines
3.6 KiB
Swift

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)
}
}