LiveActivity.swift 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import ActivityKit
  2. import Charts
  3. import SwiftUI
  4. import WidgetKit
  5. struct LiveActivity: Widget {
  6. let dateFormatter: DateFormatter = {
  7. var f = DateFormatter()
  8. f.dateStyle = .none
  9. f.timeStyle = .short
  10. return f
  11. }()
  12. func changeLabel(context: ActivityViewContext<LiveActivityAttributes>) -> Text {
  13. if !context.isStale && !context.state.change.isEmpty {
  14. Text(context.state.change)
  15. } else {
  16. Text("--")
  17. }
  18. }
  19. func updatedLabel(context: ActivityViewContext<LiveActivityAttributes>) -> Text {
  20. Text("Updated: \(dateFormatter.string(from: context.state.date))")
  21. }
  22. func bgLabel(context: ActivityViewContext<LiveActivityAttributes>) -> Text {
  23. if context.isStale {
  24. Text("--")
  25. } else {
  26. Text(context.state.bg).fontWeight(.bold)
  27. }
  28. }
  29. @ViewBuilder func trend(context: ActivityViewContext<LiveActivityAttributes>) -> some View {
  30. if context.isStale {
  31. Text("--")
  32. } else {
  33. if let trendSystemImage = context.state.trendSystemImage {
  34. Image(systemName: trendSystemImage)
  35. }
  36. }
  37. }
  38. @ViewBuilder func bgAndTrend(context: ActivityViewContext<LiveActivityAttributes>) -> some View {
  39. if context.isStale {
  40. Text("--")
  41. } else {
  42. HStack {
  43. Text(context.state.bg).fontWeight(.bold)
  44. if let trendSystemImage = context.state.trendSystemImage {
  45. Image(systemName: trendSystemImage)
  46. }
  47. }
  48. }
  49. }
  50. @ViewBuilder func bobble(context: ActivityViewContext<LiveActivityAttributes>) -> some View {
  51. @State var angularGradient = AngularGradient(colors: [
  52. Color(red: 0.7215686275, green: 0.3411764706, blue: 1),
  53. Color(red: 0.6235294118, green: 0.4235294118, blue: 0.9803921569),
  54. Color(red: 0.4862745098, green: 0.5450980392, blue: 0.9529411765),
  55. Color(red: 0.3411764706, green: 0.6666666667, blue: 0.9254901961),
  56. Color(red: 0.262745098, green: 0.7333333333, blue: 0.9137254902),
  57. Color(red: 0.7215686275, green: 0.3411764706, blue: 1)
  58. ], center: .center, startAngle: .degrees(270), endAngle: .degrees(-90))
  59. let triangleColor = Color(red: 0.262745098, green: 0.7333333333, blue: 0.9137254902)
  60. WidgetBobble(gradient: angularGradient, color: triangleColor)
  61. .rotationEffect(.degrees(context.state.rotationDegrees))
  62. }
  63. @ViewBuilder func chart(context: ActivityViewContext<LiveActivityAttributes>) -> some View {
  64. if context.isStale {
  65. Text("No data available")
  66. } else {
  67. Chart {
  68. ForEach(context.state.chart.indices, id: \.self) { index in
  69. let currentValue = context.state.chart[index]
  70. if currentValue > context.state.highGlucose {
  71. PointMark(
  72. x: .value("Time", context.state.chartDate[index] ?? Date()),
  73. y: .value("Value", currentValue)
  74. ).foregroundStyle(Color.orange.gradient).symbolSize(12)
  75. } else if currentValue < context.state.lowGlucose {
  76. PointMark(
  77. x: .value("Time", context.state.chartDate[index] ?? Date()),
  78. y: .value("Value", currentValue)
  79. ).foregroundStyle(Color.red.gradient).symbolSize(12)
  80. } else {
  81. PointMark(
  82. x: .value("Time", context.state.chartDate[index] ?? Date()),
  83. y: .value("Value", currentValue)
  84. ).foregroundStyle(Color.green.gradient).symbolSize(12)
  85. }
  86. }
  87. }.chartPlotStyle { plotContent in
  88. plotContent.background(.cyan.opacity(0.1))
  89. }
  90. .chartYAxis {
  91. AxisMarks(position: .leading) { _ in
  92. AxisValueLabel().foregroundStyle(Color.white)
  93. AxisGridLine(stroke: .init(lineWidth: 0.1, dash: [2, 3])).foregroundStyle(Color.white)
  94. }
  95. }
  96. .chartXAxis {
  97. AxisMarks(position: .automatic) { _ in
  98. AxisValueLabel().foregroundStyle(Color.white)
  99. AxisGridLine(stroke: .init(lineWidth: 0.1, dash: [2, 3])).foregroundStyle(Color.white)
  100. }
  101. }
  102. }
  103. }
  104. var body: some WidgetConfiguration {
  105. ActivityConfiguration(for: LiveActivityAttributes.self) { context in
  106. // Lock screen/banner UI goes here
  107. HStack(spacing: 2) {
  108. VStack {
  109. chart(context: context).frame(width: UIScreen.main.bounds.width / 1.8)
  110. }.padding(.vertical, 5).padding(.horizontal, 15)
  111. Divider().foregroundStyle(Color.white)
  112. VStack {
  113. ZStack {
  114. bobble(context: context)
  115. .scaleEffect(0.6)
  116. .clipped()
  117. VStack {
  118. // bgAndTrend(context: context).imageScale(.small).font(.title2)
  119. bgLabel(context: context).font(.title2).imageScale(.small)
  120. changeLabel(context: context).font(.callout)
  121. }
  122. }.padding(.trailing, 10).padding(.top, 5)
  123. updatedLabel(context: context).font(.caption).padding(.bottom).padding(.trailing, 5)
  124. }
  125. }
  126. .privacySensitive()
  127. .imageScale(.small)
  128. .padding(.all, 15)
  129. .background(Color.white.opacity(0.2))
  130. .foregroundColor(Color.white)
  131. .activityBackgroundTint(Color.black.opacity(0.7))
  132. .activitySystemActionForegroundColor(Color.white)
  133. } dynamicIsland: { context in
  134. DynamicIsland {
  135. // Expanded UI goes here. Compose the expanded UI through
  136. // various regions, like leading/trailing/center/bottom
  137. DynamicIslandExpandedRegion(.leading) {
  138. HStack(spacing: 3) {
  139. bgAndTrend(context: context)
  140. }.imageScale(.small).font(.title).padding(.leading, 5)
  141. }
  142. DynamicIslandExpandedRegion(.trailing) {
  143. changeLabel(context: context).font(.title).padding(.trailing, 5)
  144. }
  145. DynamicIslandExpandedRegion(.bottom) {
  146. chart(context: context)
  147. }
  148. DynamicIslandExpandedRegion(.center) {
  149. updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary)
  150. }
  151. } compactLeading: {
  152. HStack(spacing: 1) {
  153. bgAndTrend(context: context)
  154. }.bold().imageScale(.small).padding(.leading, 5)
  155. } compactTrailing: {
  156. changeLabel(context: context).padding(.trailing, 5)
  157. } minimal: {
  158. bgLabel(context: context).bold()
  159. }
  160. .widgetURL(URL(string: "freeaps-x://"))
  161. .keylineTint(Color.cyan.opacity(0.5))
  162. }
  163. }
  164. }
  165. // private extension LiveActivityAttributes {
  166. // static var preview: LiveActivityAttributes {
  167. // LiveActivityAttributes(startDate: Date())
  168. // }
  169. // }
  170. //
  171. // private extension LiveActivityAttributes.ContentState {
  172. // static var test: LiveActivityAttributes.ContentState {
  173. // LiveActivityAttributes.ContentState(bg: "100", trendSystemImage: "arrow.right", change: "+2", date: Date())
  174. // }
  175. // }
  176. //
  177. // #Preview("Notification", as: .content, using: LiveActivityAttributes.preview) {
  178. // LiveActivity()
  179. // } contentStates: {
  180. // LiveActivityAttributes.ContentState.test
  181. // }