TrendShape.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import SwiftUI
  2. struct Triangle: Shape {
  3. /// Flag to be able to adjust size based on Apple Watch size
  4. let deviceType: WatchSize
  5. private var triangleTipFactor: CGFloat {
  6. switch deviceType {
  7. case .watch40mm,
  8. .watch41mm,
  9. .watch42mm:
  10. return 7.5
  11. case .unknown,
  12. .watch44mm,
  13. .watch45mm:
  14. return 9
  15. case .watch49mm:
  16. return 9
  17. }
  18. }
  19. private var triangleBezierFactor: CGFloat {
  20. switch deviceType {
  21. case .watch40mm,
  22. .watch41mm,
  23. .watch42mm:
  24. return 5
  25. case .unknown,
  26. .watch44mm,
  27. .watch45mm:
  28. return 7
  29. case .watch49mm:
  30. return 7
  31. }
  32. }
  33. /// Creates a triangle shape pointing to the right
  34. func path(in rect: CGRect) -> Path {
  35. var path = Path()
  36. // Draw the triangle pointing to the right
  37. path.move(to: CGPoint(x: rect.maxX - triangleTipFactor, y: rect.midY))
  38. path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
  39. path.addQuadCurve(
  40. to: CGPoint(x: rect.minX, y: rect.maxY),
  41. control: CGPoint(x: rect.midX - triangleBezierFactor, y: rect.midY)
  42. )
  43. path.closeSubpath()
  44. return path
  45. }
  46. }
  47. /// A view that displays a circular trend indicator with a directional triangle
  48. struct TrendShape: View {
  49. /// Rotation angle in degrees for the trend direction
  50. let rotationDegrees: Double
  51. /// Flag to be able to adjust size based on Apple Watch size
  52. let deviceType: WatchSize
  53. // Angular gradient for the outer circle, transitioning through various blues and purples
  54. private let angularGradient = AngularGradient(
  55. colors: [
  56. Color(red: 0.7215686275, green: 0.3411764706, blue: 1), // #B857FF
  57. Color(red: 0.6235294118, green: 0.4235294118, blue: 0.9803921569), // #9F6CFA
  58. Color(red: 0.4862745098, green: 0.5450980392, blue: 0.9529411765), // #7C8BF3
  59. Color(red: 0.3411764706, green: 0.6666666667, blue: 0.9254901961), // #57AAEC
  60. Color(red: 0.262745098, green: 0.7333333333, blue: 0.9137254902), // #43BBE9
  61. Color(red: 0.7215686275, green: 0.3411764706, blue: 1) // #B857FF (repeated for seamless transition)
  62. ],
  63. center: .center,
  64. startAngle: .degrees(270),
  65. endAngle: .degrees(-90)
  66. )
  67. // Color for the direction indicator triangle
  68. private let triangleColor = Color(red: 0.262745098, green: 0.7333333333, blue: 0.9137254902) // #43BBE9
  69. private var strokeWidth: CGFloat {
  70. switch deviceType {
  71. case .watch40mm:
  72. return 3
  73. case .watch41mm,
  74. .watch42mm:
  75. return 4
  76. case .unknown,
  77. .watch44mm,
  78. .watch45mm:
  79. return 4
  80. case .watch49mm:
  81. return 5
  82. }
  83. }
  84. private var circleSize: CGFloat {
  85. switch deviceType {
  86. case .watch40mm:
  87. return 72
  88. case .watch41mm,
  89. .watch42mm:
  90. return 74
  91. case .watch44mm:
  92. return 82
  93. case .unknown,
  94. .watch45mm:
  95. return 90
  96. case .watch49mm:
  97. return 92
  98. }
  99. }
  100. private var triangleSize: CGFloat {
  101. switch deviceType {
  102. case .watch40mm,
  103. .watch41mm,
  104. .watch42mm:
  105. return 16
  106. case .watch44mm:
  107. return 18
  108. case .unknown,
  109. .watch45mm:
  110. return 20
  111. case .watch49mm:
  112. return 20
  113. }
  114. }
  115. private var triangleOffset: CGFloat {
  116. switch deviceType {
  117. case .watch40mm:
  118. return 46
  119. case .watch41mm,
  120. .watch42mm:
  121. return 47.5
  122. case .watch44mm:
  123. return 53.5
  124. case .unknown,
  125. .watch45mm:
  126. return 58
  127. case .watch49mm:
  128. return 59
  129. }
  130. }
  131. var body: some View {
  132. ZStack {
  133. // Outer circle with gradient
  134. Circle()
  135. .stroke(angularGradient, lineWidth: strokeWidth)
  136. .frame(width: circleSize, height: circleSize)
  137. .background(Circle().fill(Color.black))
  138. // Triangle with the color of the last gradient color
  139. Triangle(deviceType: deviceType)
  140. .fill(triangleColor)
  141. .frame(width: triangleSize, height: triangleSize)
  142. .offset(x: triangleOffset)
  143. }
  144. .rotationEffect(.degrees(rotationDegrees))
  145. .shadow(color: Color.black.opacity(0.33), radius: 3)
  146. }
  147. }