LiveActivity.swift 11 KB

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