LiveActivity.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import ActivityKit
  2. import SwiftUI
  3. import WidgetKit
  4. struct LiveActivity: Widget {
  5. var body: some WidgetConfiguration {
  6. ActivityConfiguration(for: LiveActivityAttributes.self) { context in
  7. LiveActivityView(context: context)
  8. } dynamicIsland: { context in
  9. let hasStaticColorScheme = context.state.glucoseColorScheme == "staticColor"
  10. var glucoseColor: Color {
  11. let state = context.state
  12. let detailedState = state.detailedViewState
  13. let isMgdL = detailedState?.unit == "mg/dL"
  14. // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
  15. let hardCodedLow = isMgdL ? Decimal(55) : 55.asMmolL
  16. let hardCodedHigh = isMgdL ? Decimal(220) : 220.asMmolL
  17. return Color.getDynamicGlucoseColor(
  18. glucoseValue: Decimal(string: state.bg) ?? 100,
  19. highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : state.highGlucose,
  20. lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : state.lowGlucose,
  21. targetGlucose: isMgdL ? state.target : state.target.asMmolL,
  22. glucoseColorScheme: state.glucoseColorScheme
  23. )
  24. }
  25. return DynamicIsland {
  26. DynamicIslandExpandedRegion(.leading) {
  27. LiveActivityExpandedLeadingView(context: context, glucoseColor: glucoseColor)
  28. }
  29. DynamicIslandExpandedRegion(.trailing) {
  30. LiveActivityExpandedTrailingView(
  31. context: context,
  32. glucoseColor: hasStaticColorScheme ? .primary : glucoseColor
  33. )
  34. }
  35. DynamicIslandExpandedRegion(.bottom) {
  36. LiveActivityExpandedBottomView(context: context)
  37. }
  38. DynamicIslandExpandedRegion(.center) {
  39. LiveActivityExpandedCenterView(context: context)
  40. }
  41. } compactLeading: {
  42. LiveActivityCompactLeadingView(context: context, glucoseColor: glucoseColor)
  43. } compactTrailing: {
  44. LiveActivityCompactTrailingView(context: context, glucoseColor: hasStaticColorScheme ? .primary : glucoseColor)
  45. } minimal: {
  46. LiveActivityMinimalView(context: context, glucoseColor: glucoseColor)
  47. }
  48. .widgetURL(URL(string: "Trio://"))
  49. .keylineTint(glucoseColor)
  50. .contentMargins(.horizontal, 0, for: .minimal)
  51. .contentMargins(.trailing, 0, for: .compactLeading)
  52. .contentMargins(.leading, 0, for: .compactTrailing)
  53. }
  54. }
  55. }
  56. // Mock structure to replace GlucoseData
  57. struct MockGlucoseData {
  58. var glucose: Int
  59. var date: Date
  60. var direction: String? // You can refine this based on your expected data
  61. }
  62. private extension LiveActivityAttributes {
  63. static var preview: LiveActivityAttributes {
  64. LiveActivityAttributes(startDate: Date())
  65. }
  66. }
  67. private extension LiveActivityAttributes.ContentState {
  68. static var chartData: [MockGlucoseData] = [
  69. MockGlucoseData(glucose: 120, date: Date().addingTimeInterval(-600), direction: "flat"),
  70. MockGlucoseData(glucose: 125, date: Date().addingTimeInterval(-300), direction: "flat"),
  71. MockGlucoseData(glucose: 130, date: Date(), direction: "flat")
  72. ]
  73. static var detailedViewState = LiveActivityAttributes.ContentAdditionalState(
  74. chart: chartData.map { Decimal($0.glucose) },
  75. chartDate: chartData.map(\.date),
  76. rotationDegrees: 0,
  77. cob: 20,
  78. iob: 1.5,
  79. unit: GlucoseUnits.mgdL.rawValue,
  80. isOverrideActive: false,
  81. overrideName: "Exercise",
  82. overrideDate: Date().addingTimeInterval(-3600),
  83. overrideDuration: 120,
  84. overrideTarget: 150,
  85. widgetItems: LiveActivityAttributes.LiveActivityItem.defaultItems
  86. )
  87. // 0 is the widest digit. Use this to get an upper bound on text width.
  88. // Use mmol/l notation with decimal point as well for the same reason, it uses up to 4 characters, while mg/dl uses up to 3
  89. static var testWide: LiveActivityAttributes.ContentState {
  90. LiveActivityAttributes.ContentState(
  91. bg: "00.0",
  92. direction: "→",
  93. change: "+0.0",
  94. date: Date(),
  95. highGlucose: 180,
  96. lowGlucose: 70,
  97. target: 100,
  98. glucoseColorScheme: "staticColor",
  99. detailedViewState: nil,
  100. isInitialState: false
  101. )
  102. }
  103. static var testVeryWide: LiveActivityAttributes.ContentState {
  104. LiveActivityAttributes.ContentState(
  105. bg: "00.0",
  106. direction: "↑↑",
  107. change: "+0.0",
  108. date: Date(),
  109. highGlucose: 180,
  110. lowGlucose: 70,
  111. target: 100,
  112. glucoseColorScheme: "staticColor",
  113. detailedViewState: nil,
  114. isInitialState: false
  115. )
  116. }
  117. static var testSuperWide: LiveActivityAttributes.ContentState {
  118. LiveActivityAttributes.ContentState(
  119. bg: "00.0",
  120. direction: "↑↑↑",
  121. change: "+0.0",
  122. date: Date(),
  123. highGlucose: 180,
  124. lowGlucose: 70,
  125. target: 100,
  126. glucoseColorScheme: "staticColor",
  127. detailedViewState: nil,
  128. isInitialState: false
  129. )
  130. }
  131. // 2 characters for BG, 1 character for change is the minimum that will be shown
  132. static var testNarrow: LiveActivityAttributes.ContentState {
  133. LiveActivityAttributes.ContentState(
  134. bg: "00",
  135. direction: "↑",
  136. change: "+0",
  137. date: Date(),
  138. highGlucose: 180,
  139. lowGlucose: 70,
  140. target: 100,
  141. glucoseColorScheme: "staticColor",
  142. detailedViewState: nil,
  143. isInitialState: false
  144. )
  145. }
  146. static var testMedium: LiveActivityAttributes.ContentState {
  147. LiveActivityAttributes.ContentState(
  148. bg: "000",
  149. direction: "↗︎",
  150. change: "+00",
  151. date: Date(),
  152. highGlucose: 180,
  153. lowGlucose: 70,
  154. target: 100,
  155. glucoseColorScheme: "staticColor",
  156. detailedViewState: nil,
  157. isInitialState: false
  158. )
  159. }
  160. static var testExpired: LiveActivityAttributes.ContentState {
  161. LiveActivityAttributes.ContentState(
  162. bg: "--",
  163. direction: nil,
  164. change: "--",
  165. date: Date().addingTimeInterval(-60 * 60),
  166. highGlucose: 180,
  167. lowGlucose: 70,
  168. target: 100,
  169. glucoseColorScheme: "staticColor",
  170. detailedViewState: nil,
  171. isInitialState: false
  172. )
  173. }
  174. static var testWideDetailed: LiveActivityAttributes.ContentState {
  175. LiveActivityAttributes.ContentState(
  176. bg: "00.0",
  177. direction: "→",
  178. change: "+0.0",
  179. date: Date(),
  180. highGlucose: 180,
  181. lowGlucose: 70,
  182. target: 100,
  183. glucoseColorScheme: "staticColor",
  184. detailedViewState: detailedViewState,
  185. isInitialState: false
  186. )
  187. }
  188. static var testVeryWideDetailed: LiveActivityAttributes.ContentState {
  189. LiveActivityAttributes.ContentState(
  190. bg: "00.0",
  191. direction: "↑↑",
  192. change: "+0.0",
  193. date: Date(),
  194. highGlucose: 180,
  195. lowGlucose: 70,
  196. target: 100,
  197. glucoseColorScheme: "staticColor",
  198. detailedViewState: detailedViewState,
  199. isInitialState: false
  200. )
  201. }
  202. static var testSuperWideDetailed: LiveActivityAttributes.ContentState {
  203. LiveActivityAttributes.ContentState(
  204. bg: "00.0",
  205. direction: "↑↑↑",
  206. change: "+0.0",
  207. date: Date(),
  208. highGlucose: 180,
  209. lowGlucose: 70,
  210. target: 100,
  211. glucoseColorScheme: "staticColor",
  212. detailedViewState: detailedViewState,
  213. isInitialState: false
  214. )
  215. }
  216. // 2 characters for BG, 1 character for change is the minimum that will be shown
  217. static var testNarrowDetailed: LiveActivityAttributes.ContentState {
  218. LiveActivityAttributes.ContentState(
  219. bg: "00",
  220. direction: "↑",
  221. change: "+0",
  222. date: Date(),
  223. highGlucose: 180,
  224. lowGlucose: 70,
  225. target: 100,
  226. glucoseColorScheme: "staticColor",
  227. detailedViewState: detailedViewState,
  228. isInitialState: false
  229. )
  230. }
  231. static var testMediumDetailed: LiveActivityAttributes.ContentState {
  232. LiveActivityAttributes.ContentState(
  233. bg: "000",
  234. direction: "↗︎",
  235. change: "+00",
  236. date: Date(),
  237. highGlucose: 180,
  238. lowGlucose: 70,
  239. target: 100,
  240. glucoseColorScheme: "staticColor",
  241. detailedViewState: detailedViewState,
  242. isInitialState: false
  243. )
  244. }
  245. static var testExpiredDetailed: LiveActivityAttributes.ContentState {
  246. LiveActivityAttributes.ContentState(
  247. bg: "--",
  248. direction: nil,
  249. change: "--",
  250. date: Date().addingTimeInterval(-60 * 60),
  251. highGlucose: 180,
  252. lowGlucose: 70,
  253. target: 100,
  254. glucoseColorScheme: "staticColor",
  255. detailedViewState: detailedViewState,
  256. isInitialState: false
  257. )
  258. }
  259. }
  260. @available(iOS 17.0, iOSApplicationExtension 17.0, *)
  261. #Preview("Simple", as: .content, using: LiveActivityAttributes.preview) {
  262. LiveActivity()
  263. } contentStates: {
  264. LiveActivityAttributes.ContentState.testSuperWide
  265. LiveActivityAttributes.ContentState.testVeryWide
  266. LiveActivityAttributes.ContentState.testWide
  267. LiveActivityAttributes.ContentState.testMedium
  268. LiveActivityAttributes.ContentState.testNarrow
  269. LiveActivityAttributes.ContentState.testExpired
  270. }
  271. @available(iOS 17.0, iOSApplicationExtension 17.0, *)
  272. #Preview("Detailed", as: .content, using: LiveActivityAttributes.preview) {
  273. LiveActivity()
  274. } contentStates: {
  275. LiveActivityAttributes.ContentState.testSuperWideDetailed
  276. LiveActivityAttributes.ContentState.testVeryWideDetailed
  277. LiveActivityAttributes.ContentState.testWideDetailed
  278. LiveActivityAttributes.ContentState.testMediumDetailed
  279. LiveActivityAttributes.ContentState.testNarrowDetailed
  280. LiveActivityAttributes.ContentState.testExpiredDetailed
  281. }