TrendShape.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. .watch41mm,
  73. .watch42mm:
  74. return 4
  75. case .unknown,
  76. .watch44mm,
  77. .watch45mm:
  78. return 5
  79. case .watch49mm:
  80. return 5
  81. }
  82. }
  83. private var circleSize: CGFloat {
  84. switch deviceType {
  85. case .watch40mm,
  86. .watch41mm,
  87. .watch42mm:
  88. return 74
  89. case .unknown,
  90. .watch44mm,
  91. .watch45mm:
  92. return 92
  93. case .watch49mm:
  94. return 92
  95. }
  96. }
  97. private var triangleSize: CGFloat {
  98. switch deviceType {
  99. case .watch40mm,
  100. .watch41mm,
  101. .watch42mm:
  102. return 16
  103. case .unknown,
  104. .watch44mm,
  105. .watch45mm:
  106. return 20
  107. case .watch49mm:
  108. return 20
  109. }
  110. }
  111. private var triangleOffset: CGFloat {
  112. switch deviceType {
  113. case .watch40mm,
  114. .watch41mm,
  115. .watch42mm:
  116. return 47.5
  117. case .unknown,
  118. .watch44mm,
  119. .watch45mm:
  120. return 59
  121. case .watch49mm:
  122. return 59
  123. }
  124. }
  125. var body: some View {
  126. ZStack {
  127. // Outer circle with gradient
  128. Circle()
  129. .stroke(angularGradient, lineWidth: strokeWidth)
  130. .frame(width: circleSize, height: circleSize)
  131. .background(Circle().fill(Color.black))
  132. // Triangle with the color of the last gradient color
  133. Triangle(deviceType: deviceType)
  134. .fill(triangleColor)
  135. .frame(width: triangleSize, height: triangleSize)
  136. .offset(x: triangleOffset)
  137. }
  138. .rotationEffect(.degrees(rotationDegrees))
  139. .shadow(color: Color.black.opacity(0.33), radius: 3)
  140. }
  141. }
  142. // MARK: - TREND SHAPE PREVIEWS
  143. struct TrendShape_Previews: PreviewProvider {
  144. static var previews: some View {
  145. Group {
  146. TrendShape(rotationDegrees: 0, deviceType: .watch40mm)
  147. .previewDisplayName("TrendShape • 40mm")
  148. TrendShape(rotationDegrees: 0, deviceType: .watch41mm)
  149. .previewDisplayName("TrendShape • 41mm")
  150. TrendShape(rotationDegrees: 0, deviceType: .watch42mm)
  151. .previewDisplayName("TrendShape • 42mm")
  152. TrendShape(rotationDegrees: 0, deviceType: .watch44mm)
  153. .previewDisplayName("TrendShape • 44mm")
  154. TrendShape(rotationDegrees: 0, deviceType: .watch45mm)
  155. .previewDisplayName("TrendShape • 45mm")
  156. TrendShape(rotationDegrees: 0, deviceType: .watch49mm)
  157. .previewDisplayName("TrendShape • 49mm")
  158. }
  159. .padding()
  160. // Optional: to let each preview "shrink to fit" rather than fill the entire simulator screen:
  161. // .previewLayout(.sizeThatFits)
  162. }
  163. }
  164. // MARK: - TRIANGLE PREVIEWS
  165. struct Triangle_Previews: PreviewProvider {
  166. static var previews: some View {
  167. Group {
  168. Triangle(deviceType: .watch40mm)
  169. .fill(Color.blue)
  170. .frame(width: 50, height: 50)
  171. .previewDisplayName("Triangle • 40mm")
  172. Triangle(deviceType: .watch41mm)
  173. .fill(Color.green)
  174. .frame(width: 50, height: 50)
  175. .previewDisplayName("Triangle • 41mm")
  176. Triangle(deviceType: .watch42mm)
  177. .fill(Color.purple)
  178. .frame(width: 50, height: 50)
  179. .previewDisplayName("Triangle • 42mm")
  180. Triangle(deviceType: .watch44mm)
  181. .fill(Color.red)
  182. .frame(width: 50, height: 50)
  183. .previewDisplayName("Triangle • 44mm")
  184. Triangle(deviceType: .watch45mm)
  185. .fill(Color.orange)
  186. .frame(width: 50, height: 50)
  187. .previewDisplayName("Triangle • 45mm")
  188. Triangle(deviceType: .watch49mm)
  189. .fill(Color.pink)
  190. .frame(width: 50, height: 50)
  191. .previewDisplayName("Triangle • 49mm")
  192. }
  193. .padding()
  194. // .previewLayout(.sizeThatFits)
  195. }
  196. }