ContactTrickPicture.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. import Foundation
  2. import SwiftUI
  3. struct ContactPicture: View {
  4. private enum Config {
  5. static let lag: TimeInterval = 30
  6. }
  7. @Binding var contact: ContactTrickEntry
  8. @Binding var state: ContactTrickState
  9. private static let formatter: DateFormatter = {
  10. let formatter = DateFormatter()
  11. formatter.dateFormat = "HH:mm"
  12. return formatter
  13. }()
  14. static func getImage(
  15. contact: ContactTrickEntry,
  16. state: ContactTrickState
  17. ) -> UIImage {
  18. let width = 1024.0
  19. let height = 1024.0
  20. var rect = CGRect(x: 0, y: 0, width: width, height: height)
  21. let textColor: Color = contact.hasHighContrast ?
  22. Color(red: 250 / 256, green: 250 / 256, blue: 250 / 256) :
  23. Color(red: 20 / 256, green: 20 / 256, blue: 20 / 256)
  24. let secondaryTextColor: Color = contact.hasHighContrast ?
  25. Color(red: 220 / 256, green: 220 / 256, blue: 220 / 256) :
  26. Color(red: 40 / 256, green: 40 / 256, blue: 40 / 256)
  27. let fontWeight = contact.fontWeight
  28. UIGraphicsBeginImageContext(rect.size)
  29. if let context = UIGraphicsGetCurrentContext() {
  30. context.setShouldAntialias(true)
  31. context.setAllowsAntialiasing(true)
  32. }
  33. let ringWidth = Double(contact.ringWidth.rawValue) / 100.0
  34. let ringGap = Double(contact.ringGap.rawValue) / 100.0
  35. let outerGap = 0.03
  36. if contact.ring != .none {
  37. rect = CGRect(
  38. x: rect.minX + width * outerGap,
  39. y: rect.minY + height * outerGap,
  40. width: rect.width - width * outerGap * 2,
  41. height: rect.height - height * outerGap * 2
  42. )
  43. let ringRect = CGRect(
  44. x: rect.minX + width * ringWidth * 0.5,
  45. y: rect.minY + height * ringWidth * 0.5,
  46. width: rect.width - width * ringWidth,
  47. height: rect.height - height * ringWidth
  48. )
  49. drawRing(ring: contact.ring, contact: contact, state: state, rect: ringRect, strokeWidth: width * ringWidth)
  50. rect = CGRect(
  51. x: rect.minX + width * (ringWidth + ringGap),
  52. y: rect.minY + height * (ringWidth + ringGap),
  53. width: rect.width - width * (ringWidth + ringGap) * 2,
  54. height: rect.height - height * (ringWidth + ringGap) * 2
  55. )
  56. }
  57. switch contact.layout {
  58. case .single:
  59. let showTop = contact.top != .none
  60. let showBottom = contact.bottom != .none
  61. let centerX = rect.minX + rect.width / 2
  62. let centerY = rect.minY + rect.height / 2
  63. let radius = min(rect.width, rect.height) / 2
  64. var primaryHeight = radius * 0.8
  65. let topHeight = radius * 0.5
  66. var bottomHeight = radius * 0.5
  67. var primaryY = centerY - primaryHeight / 2
  68. if contact.bottom == .none, contact.top != .none {
  69. primaryY += radius * 0.2
  70. }
  71. if contact.bottom != .none, contact.top == .none {
  72. primaryY -= radius * 0.2
  73. }
  74. let topY = primaryY - topHeight
  75. var bottomY = primaryY + primaryHeight
  76. let primaryWidth = 2 * sqrt(radius * radius - (primaryHeight * 0.5) * (primaryHeight * 0.5))
  77. let topWidth = 2 *
  78. sqrt(radius * radius - (topHeight + primaryHeight * 0.5) * (topHeight + primaryHeight * 0.5))
  79. var bottomWidth = 2 *
  80. sqrt(radius * radius - (bottomHeight + primaryHeight * 0.5) * (bottomHeight + primaryHeight * 0.5))
  81. if contact.bottom != .none, contact.top == .none {
  82. // move things around a little bit to give more space to the bottom area
  83. // TODO: revisit rings for iob, cob and combined iob+cob with more user feedback
  84. if contact.bottom == .trend, contact.ring == .loop {
  85. // if contact.ring == .iob || contact.ring == .cob || contact.ring == .iobcob ||
  86. // (contact.bottom == .trend && contact.ring == .loop)
  87. // {
  88. bottomHeight = bottomHeight + height * ringWidth * 2
  89. bottomWidth = bottomWidth + width * ringWidth * 2
  90. } else if contact.ring == .loop {
  91. primaryHeight = primaryHeight - height * ringWidth
  92. bottomY = primaryY + primaryHeight
  93. bottomHeight = bottomHeight + height * ringWidth * 2
  94. bottomWidth = bottomWidth + width * ringWidth * 2
  95. }
  96. }
  97. let primaryRect = (showTop || showBottom) ? CGRect(
  98. x: centerX - primaryWidth * 0.5,
  99. y: primaryY,
  100. width: primaryWidth,
  101. height: primaryHeight
  102. ) : rect
  103. let topRect = CGRect(
  104. x: centerX - topWidth * 0.5,
  105. y: topY,
  106. width: topWidth,
  107. height: topHeight
  108. )
  109. let bottomRect = CGRect(
  110. x: centerX - bottomWidth * 0.5,
  111. y: bottomY,
  112. width: bottomWidth,
  113. height: bottomHeight
  114. )
  115. let secondaryFontSize = contact.secondaryFontSize
  116. displayPiece(
  117. value: contact.primary,
  118. contact: contact,
  119. state: state,
  120. rect: primaryRect,
  121. fitHeigh: false,
  122. fontSize: contact.fontSize.rawValue,
  123. fontWeight: fontWeight,
  124. fontWidth: contact.fontWidth,
  125. color: textColor
  126. )
  127. if showTop {
  128. displayPiece(
  129. value: contact.top,
  130. contact: contact,
  131. state: state,
  132. rect: topRect,
  133. fitHeigh: true,
  134. fontSize: secondaryFontSize.rawValue,
  135. fontWeight: fontWeight,
  136. fontWidth: contact.fontWidth,
  137. color: secondaryTextColor
  138. )
  139. }
  140. if showBottom {
  141. displayPiece(
  142. value: contact.bottom,
  143. contact: contact,
  144. state: state,
  145. rect: bottomRect,
  146. fitHeigh: true,
  147. fontSize: secondaryFontSize.rawValue,
  148. fontWeight: fontWeight,
  149. fontWidth: contact.fontWidth,
  150. color: secondaryTextColor
  151. )
  152. }
  153. case .split:
  154. let centerX = rect.origin.x + rect.size.width / 2
  155. let centerY = rect.origin.y + rect.size.height / 2
  156. let radius = min(rect.size.width, rect.size.height) / 2
  157. let rectangleHeight = radius * sqrt(2) / 2
  158. let rectangleWidth = sqrt(2) * radius
  159. let topY = centerY - rectangleHeight
  160. let bottomY = centerY
  161. let topRect = CGRect(
  162. x: centerX - rectangleWidth / 2,
  163. y: topY,
  164. width: rectangleWidth,
  165. height: rectangleHeight
  166. )
  167. let bottomRect = CGRect(
  168. x: centerX - rectangleWidth / 2,
  169. y: bottomY,
  170. width: rectangleWidth,
  171. height: rectangleHeight
  172. )
  173. let topFontSize = contact.fontSize
  174. let bottomFontSize = contact.secondaryFontSize
  175. displayPiece(
  176. value: contact.top,
  177. contact: contact,
  178. state: state,
  179. rect: topRect,
  180. fitHeigh: true,
  181. fontSize: topFontSize.rawValue,
  182. fontWeight: fontWeight,
  183. fontWidth: contact.fontWidth,
  184. color: textColor
  185. )
  186. displayPiece(
  187. value: contact.bottom,
  188. contact: contact,
  189. state: state,
  190. rect: bottomRect,
  191. fitHeigh: true,
  192. fontSize: bottomFontSize.rawValue,
  193. fontWeight: fontWeight,
  194. fontWidth: contact.fontWidth,
  195. color: textColor
  196. )
  197. }
  198. let image = UIGraphicsGetImageFromCurrentImageContext()
  199. UIGraphicsEndImageContext()
  200. return image ?? UIImage()
  201. }
  202. private static func displayPiece(
  203. value: ContactTrickValue,
  204. contact: ContactTrickEntry,
  205. state: ContactTrickState,
  206. rect: CGRect,
  207. fitHeigh: Bool,
  208. fontSize: Int,
  209. fontWeight: Font.Weight,
  210. fontWidth: Font.Width,
  211. color: Color
  212. ) {
  213. guard value != .none else { return }
  214. if value == .ring {
  215. drawRing(
  216. ring: .loop,
  217. contact: contact,
  218. state: state,
  219. rect: CGRect(
  220. x: rect.minX + rect.width * 0.10,
  221. y: rect.minY + rect.height * 0.10,
  222. width: rect.width * 0.80,
  223. height: rect.height * 0.80
  224. ),
  225. strokeWidth: 10.0
  226. )
  227. return
  228. }
  229. let text: String? = switch value {
  230. case .glucose: state.glucose
  231. case .eventualBG: state.eventualBG
  232. case .delta: state.delta
  233. case .trend: state.trend
  234. case .lastLoopDate: state.lastLoopDate.map({ formatter.string(from: $0) })
  235. case .cob: state.cobText
  236. case .iob: state.iobText
  237. default: nil
  238. }
  239. let glucoseValue = Decimal(string: state.glucose ?? "100") ?? 100
  240. let dynamicColor: Color = FreeAPS.getDynamicGlucoseColor(
  241. glucoseValue: glucoseValue,
  242. highGlucoseColorValue: state.highGlucoseColorValue,
  243. lowGlucoseColorValue: state.lowGlucoseColorValue,
  244. targetGlucose: state.targetGlucose,
  245. glucoseColorScheme: state.glucoseColorScheme
  246. )
  247. let textColor: Color = switch value {
  248. case .cob:
  249. .loopYellow
  250. case .glucose:
  251. dynamicColor
  252. default:
  253. color
  254. }
  255. if let text = text {
  256. drawText(
  257. text: text,
  258. rect: rect,
  259. fitHeigh: fitHeigh,
  260. fontSize: fontSize,
  261. fontWeight: fontWeight,
  262. fontWidth: fontWidth,
  263. color: textColor
  264. )
  265. }
  266. }
  267. private static func drawText(
  268. text: String,
  269. rect: CGRect,
  270. fitHeigh: Bool,
  271. fontSize: Int,
  272. fontWeight: Font.Weight,
  273. fontWidth: Font.Width,
  274. color: Color
  275. ) {
  276. var theFontSize = fontSize
  277. func makeAttributes(_ size: Int) -> [NSAttributedString.Key: Any] {
  278. let font = UIFont.systemFont(ofSize: CGFloat(size), weight: fontWeight.uiFontWeight)
  279. return [
  280. .font: font,
  281. .foregroundColor: UIColor(color),
  282. .kern: fontWidth.value * Double(fontSize) // `kern` is the correct key for tracking
  283. ]
  284. }
  285. var attributes: [NSAttributedString.Key: Any] = makeAttributes(theFontSize)
  286. var stringSize = text.size(withAttributes: attributes)
  287. while stringSize.width > rect.width * 0.90 || fitHeigh && (stringSize.height > rect.height * 0.95), theFontSize > 50 {
  288. theFontSize -= 10
  289. attributes = makeAttributes(theFontSize)
  290. stringSize = text.size(withAttributes: attributes)
  291. }
  292. text.draw(
  293. in: CGRect(
  294. x: rect.minX + (rect.width - stringSize.width) / 2,
  295. y: rect.minY + (rect.height - stringSize.height) / 2,
  296. width: stringSize.width,
  297. height: stringSize.height
  298. ),
  299. withAttributes: attributes
  300. )
  301. }
  302. private static func drawRing(
  303. ring: ContactTrickLargeRing,
  304. contact: ContactTrickEntry,
  305. state: ContactTrickState,
  306. rect: CGRect,
  307. strokeWidth: Double
  308. ) {
  309. guard let context = UIGraphicsGetCurrentContext() else {
  310. return
  311. }
  312. switch ring {
  313. case .loop:
  314. let color = ringColor(contact: contact, state: state)
  315. let strokeWidth = strokeWidth
  316. let center = CGPoint(x: rect.midX, y: rect.midY)
  317. let radius = min(rect.width, rect.height) / 2 - strokeWidth / 2
  318. context.setLineWidth(strokeWidth)
  319. context.setStrokeColor(UIColor(color).cgColor)
  320. context.addArc(center: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: false)
  321. context.strokePath()
  322. // case .iob:
  323. // if let iob = state.iob, state.maxIOB > 0.1 {
  324. // drawProgressBar(
  325. // rect: rect,
  326. // progress: Double(iob) / Double(state.maxIOB),
  327. // colors: [contact.hasHighContrast ? .blue : .blue, contact.hasHighContrast ? .pink : .red],
  328. // strokeWidth: strokeWidth
  329. // )
  330. // }
  331. // case .cob:
  332. // if let cob = state.cob, state.maxCOB > 0.01 {
  333. // drawProgressBar(
  334. // rect: rect,
  335. // progress: Double(cob) / Double(state.maxCOB),
  336. // colors: [.loopYellow, .red],
  337. // strokeWidth: strokeWidth
  338. // )
  339. // }
  340. // case .iobcob:
  341. // if state.maxIOB > 0.01, state.maxCOB > 0.01 {
  342. // drawDoubleProgressBar(
  343. // rect: rect,
  344. // progress1: state.iob.map { Double($0) / Double(state.maxIOB) },
  345. // progress2: state.cob.map { Double($0) / Double(state.maxCOB) },
  346. // colors1: [contact.hasHighContrast ? .blue : .blue, contact.hasHighContrast ? .pink : .red],
  347. // colors2: [.loopYellow, .red],
  348. // strokeWidth: strokeWidth
  349. // )
  350. // }
  351. default:
  352. break
  353. }
  354. }
  355. private static func drawProgressBar(
  356. rect: CGRect,
  357. progress: Double,
  358. colors: [Color],
  359. strokeWidth: Double
  360. ) {
  361. let startAngle: CGFloat = -(.pi + .pi / 4.0)
  362. let endAngle: CGFloat = .pi / 4.0
  363. drawGradientArc(
  364. rect: rect,
  365. progress: progress,
  366. colors: colors,
  367. strokeWidth: strokeWidth,
  368. startAngle: startAngle,
  369. endAngle: endAngle,
  370. gradientDirection: .leftToRight
  371. )
  372. }
  373. private static func drawDoubleProgressBar(
  374. rect: CGRect,
  375. progress1: Double?,
  376. progress2: Double?,
  377. colors1: [Color],
  378. colors2: [Color],
  379. strokeWidth: Double
  380. ) {
  381. if let progress1 = progress1 {
  382. let startAngle1: CGFloat = .pi / 2 + .pi / 5
  383. let endAngle1: CGFloat = 3 * .pi / 2 - .pi / 5
  384. drawGradientArc(
  385. rect: rect,
  386. progress: progress1,
  387. colors: colors1,
  388. strokeWidth: strokeWidth,
  389. startAngle: startAngle1,
  390. endAngle: endAngle1,
  391. gradientDirection: .bottomToTop
  392. )
  393. }
  394. if let progress2 = progress2 {
  395. let startAngle2: CGFloat = .pi / 2 - .pi / 5
  396. let endAngle2: CGFloat = -.pi / 2 + .pi / 5
  397. drawGradientArc(
  398. rect: rect,
  399. progress: progress2,
  400. colors: colors2,
  401. strokeWidth: strokeWidth,
  402. startAngle: startAngle2,
  403. endAngle: endAngle2,
  404. gradientDirection: .bottomToTop
  405. )
  406. }
  407. }
  408. private static func drawGradientArc(
  409. rect: CGRect,
  410. progress: Double,
  411. colors: [Color],
  412. strokeWidth: Double,
  413. startAngle: Double,
  414. endAngle: Double,
  415. gradientDirection: GradientDirection
  416. ) {
  417. guard let context = UIGraphicsGetCurrentContext() else {
  418. return
  419. }
  420. let colors = colors.map { c in UIColor(c).cgColor }
  421. let locations: [CGFloat] = [0.0, 1.0]
  422. guard let gradient = CGGradient(
  423. colorsSpace: CGColorSpaceCreateDeviceRGB(),
  424. colors: colors as CFArray,
  425. locations: locations
  426. ) else {
  427. return
  428. }
  429. context.saveGState()
  430. let center = CGPoint(x: rect.midX, y: rect.midY)
  431. let radius = min(rect.width, rect.height) / 2 - strokeWidth / 2
  432. // angle - The angle to the starting point of the arc, measured in radians from the positive x-axis.
  433. context.setLineWidth(strokeWidth)
  434. context.setLineCap(.round)
  435. let circumference = 2 * .pi * radius
  436. let offsetAngle = (strokeWidth / circumference * 1.1) * 2 * .pi
  437. let (start, middle, end) = if startAngle > endAngle {
  438. (
  439. endAngle,
  440. startAngle - (startAngle - endAngle) * max(min(progress, 1.0), 0.0),
  441. startAngle
  442. )
  443. } else {
  444. (
  445. startAngle,
  446. startAngle + (endAngle - startAngle) * max(min(progress, 1.0), 0.0),
  447. endAngle
  448. )
  449. }
  450. if start < middle - offsetAngle {
  451. let arcPath1 = UIBezierPath()
  452. arcPath1.addArc(
  453. withCenter: center,
  454. radius: radius,
  455. startAngle: start,
  456. endAngle: middle - offsetAngle,
  457. clockwise: true
  458. )
  459. context.addPath(arcPath1.cgPath)
  460. }
  461. if middle + offsetAngle < end {
  462. let arcPath2 = UIBezierPath()
  463. arcPath2.addArc(
  464. withCenter: center,
  465. radius: radius,
  466. startAngle: middle + offsetAngle,
  467. endAngle: end,
  468. clockwise: true
  469. )
  470. context.addPath(arcPath2.cgPath)
  471. }
  472. context.replacePathWithStrokedPath()
  473. context.clip()
  474. switch gradientDirection {
  475. case .bottomToTop:
  476. context.drawLinearGradient(
  477. gradient,
  478. start: CGPoint(x: rect.midX, y: rect.maxY),
  479. end: CGPoint(x: rect.midX, y: rect.minY),
  480. options: []
  481. )
  482. case .leftToRight:
  483. context.drawLinearGradient(
  484. gradient,
  485. start: CGPoint(x: rect.minX, y: rect.midY),
  486. end: CGPoint(x: rect.maxX, y: rect.midY),
  487. options: []
  488. )
  489. }
  490. context.resetClip()
  491. let circleCenter = CGPoint(
  492. x: center.x + radius * cos(middle),
  493. y: center.y + radius * sin(middle)
  494. )
  495. context.setLineWidth(strokeWidth * 0.7)
  496. context.setStrokeColor(UIColor.white.cgColor)
  497. context.addArc(
  498. center: circleCenter,
  499. radius: 0,
  500. startAngle: 0,
  501. endAngle: .pi * 2,
  502. clockwise: true
  503. )
  504. context.strokePath()
  505. context.restoreGState()
  506. }
  507. private static func ringColor(
  508. contact _: ContactTrickEntry,
  509. state: ContactTrickState
  510. ) -> Color {
  511. guard let lastLoopDate = state.lastLoopDate else {
  512. return .loopGray
  513. }
  514. let delta = Date().timeIntervalSince(lastLoopDate) - Config.lag
  515. if delta <= 5.minutes.timeInterval {
  516. return .loopGreen
  517. } else if delta <= 10.minutes.timeInterval {
  518. return .loopYellow
  519. } else {
  520. return .loopRed
  521. }
  522. }
  523. var uiImage: UIImage {
  524. ContactPicture.getImage(contact: contact, state: state)
  525. }
  526. var body: some View {
  527. Image(uiImage: uiImage)
  528. .frame(width: 256, height: 256)
  529. }
  530. }
  531. extension Font.Weight {
  532. var uiFontWeight: UIFont.Weight {
  533. switch self {
  534. case .ultraLight: return .ultraLight
  535. case .thin: return .thin
  536. case .light: return .light
  537. case .regular: return .regular
  538. case .medium: return .medium
  539. case .semibold: return .semibold
  540. case .bold: return .bold
  541. case .heavy: return .heavy
  542. case .black: return .black
  543. default: return .regular
  544. }
  545. }
  546. }
  547. enum GradientDirection: Int {
  548. case leftToRight
  549. case bottomToTop
  550. }
  551. struct ContactPicturePreview: View {
  552. @Binding var contact: ContactTrickEntry
  553. @Binding var state: ContactTrickState
  554. var body: some View {
  555. ZStack {
  556. ContactPicture(contact: $contact, state: $state)
  557. Circle()
  558. .stroke(lineWidth: 20)
  559. .foregroundColor(.white)
  560. }
  561. .frame(width: 256, height: 256)
  562. .clipShape(Circle())
  563. .preferredColorScheme($contact.wrappedValue.hasHighContrast ? .dark : .light)
  564. }
  565. }
  566. struct ContactPicture_Previews: PreviewProvider {
  567. struct Preview: View {
  568. @State var rangeIndicator: Bool = true
  569. @State var hasHighContrast: Bool = true
  570. @State var fontSize: ContactTrickEntry.FontSize = .small
  571. @State var fontWeight: UIFont.Weight = .bold
  572. @State var fontName: String? = "AmericanTypewriter"
  573. var body: some View {
  574. ContactPicturePreview(
  575. contact: .constant(
  576. ContactTrickEntry(
  577. primary: .glucose,
  578. top: .delta,
  579. bottom: .trend,
  580. fontSize: fontSize,
  581. fontWeight: .medium
  582. )
  583. ),
  584. state: .constant(ContactTrickState(
  585. glucose: "6.8",
  586. trend: "↗︎",
  587. delta: "+0.2",
  588. cob: 25,
  589. cobText: "25"
  590. ))
  591. ).previewDisplayName("bg + trend + delta")
  592. // ContactPicturePreview(
  593. // contact: .constant(
  594. // ContactTrickEntry(
  595. // ring: .iob,
  596. // primary: .glucose,
  597. // bottom: .trend,
  598. // fontSize: fontSize,
  599. // fontWeight: .medium
  600. // )
  601. // ),
  602. // state: .constant(ContactTrickState(
  603. // glucose: "6.8",
  604. // trend: "↗︎",
  605. // iob: 6.1,
  606. // iobText: "6.1",
  607. // maxIOB: 8.0
  608. // ))
  609. // ).previewDisplayName("bg + trend + iob ring")
  610. ContactPicturePreview(
  611. contact: .constant(
  612. ContactTrickEntry(
  613. primary: .glucose,
  614. top: .ring,
  615. bottom: .trend,
  616. fontSize: fontSize,
  617. fontWeight: .medium
  618. )
  619. ),
  620. state: .constant(ContactTrickState(
  621. glucose: "6.8",
  622. trend: "↗︎",
  623. lastLoopDate: .now
  624. ))
  625. ).previewDisplayName("bg + trend + ring")
  626. ContactPicturePreview(
  627. contact: .constant(
  628. ContactTrickEntry(
  629. ring: .loop,
  630. primary: .glucose,
  631. top: .none,
  632. bottom: .trend,
  633. fontSize: fontSize,
  634. fontWeight: .medium
  635. )
  636. ),
  637. state: .constant(ContactTrickState(
  638. glucose: "8.8",
  639. trend: "→",
  640. lastLoopDate: .now
  641. ))
  642. ).previewDisplayName("bg + trend + ring")
  643. ContactPicturePreview(
  644. contact: .constant(
  645. ContactTrickEntry(
  646. ring: .loop,
  647. primary: .glucose,
  648. top: .none,
  649. bottom: .eventualBG,
  650. fontSize: fontSize,
  651. fontWeight: .medium
  652. )
  653. ),
  654. state: .constant(ContactTrickState(
  655. glucose: "6.8",
  656. lastLoopDate: .now - 7.minutes,
  657. eventualBG: "6.2"
  658. ))
  659. ).previewDisplayName("bg + eventual + ring")
  660. ContactPicturePreview(
  661. contact: .constant(
  662. ContactTrickEntry(
  663. ring: .loop,
  664. primary: .lastLoopDate,
  665. top: .none,
  666. bottom: .none,
  667. fontSize: fontSize,
  668. fontWeight: .medium
  669. )
  670. ),
  671. state: .constant(ContactTrickState(
  672. glucose: "6.8",
  673. trend: "↗︎",
  674. lastLoopDate: .now - 2.minutes
  675. ))
  676. ).previewDisplayName("lastLoopDate + ring")
  677. ContactPicturePreview(
  678. contact: .constant(
  679. ContactTrickEntry(
  680. ring: .loop,
  681. primary: .glucose,
  682. top: .none,
  683. bottom: .none,
  684. fontSize: fontSize,
  685. fontWeight: .medium
  686. )
  687. ),
  688. state: .constant(ContactTrickState(
  689. glucose: "6.8",
  690. lastLoopDate: .now,
  691. iob: 6.1,
  692. iobText: "6.1",
  693. maxIOB: 8.0
  694. ))
  695. ).previewDisplayName("bg + ring + ring2")
  696. ContactPicturePreview(
  697. contact: .constant(
  698. ContactTrickEntry(
  699. layout: .split,
  700. top: .iob,
  701. bottom: .cob,
  702. fontSize: fontSize,
  703. fontWeight: .medium
  704. )
  705. ),
  706. state: .constant(ContactTrickState(
  707. iob: 1.5,
  708. iobText: "1.5",
  709. cob: 25,
  710. cobText: "25"
  711. ))
  712. ).previewDisplayName("iob + cob")
  713. // ContactPicturePreview(
  714. // contact: .constant(
  715. // ContactTrickEntry(
  716. // layout: .single,
  717. // ring: .iobcob,
  718. // primary: .none,
  719. // ringWidth: .regular,
  720. // ringGap: .regular,
  721. // fontSize: fontSize,
  722. // fontWeight: .medium
  723. // )
  724. // ),
  725. // state: .constant(ContactTrickState(
  726. // iob: 1,
  727. // iobText: "5.5",
  728. // cob: 25,
  729. // cobText: "25",
  730. // maxIOB: 10,
  731. // maxCOB: 120
  732. // ))
  733. // ).previewDisplayName("iobcob ring")
  734. //
  735. // ContactPicturePreview(
  736. // contact: .constant(
  737. // ContactTrickEntry(
  738. // layout: .single,
  739. // ring: .iobcob,
  740. // primary: .none,
  741. // fontSize: fontSize,
  742. // fontWeight: .medium
  743. // )
  744. // ),
  745. // state: .constant(ContactTrickState(
  746. // iob: -0.2,
  747. // iobText: "0.0",
  748. // cob: 0,
  749. // cobText: "0",
  750. // maxIOB: 10,
  751. // maxCOB: 120
  752. // ))
  753. // ).previewDisplayName("iobcob ring (0/0)")
  754. //
  755. // ContactPicturePreview(
  756. // contact: .constant(
  757. // ContactTrickEntry(
  758. // layout: .single,
  759. // ring: .iobcob,
  760. // primary: .none,
  761. // fontSize: fontSize,
  762. // fontWeight: .medium
  763. // )
  764. // ),
  765. // state: .constant(ContactTrickState(
  766. // iob: 10,
  767. // iobText: "0.0",
  768. // cob: 120,
  769. // cobText: "0",
  770. // maxIOB: 10,
  771. // maxCOB: 120
  772. // ))
  773. // ).previewDisplayName("iobcob ring (max/max)")
  774. //
  775. // ContactPicturePreview(
  776. // contact: .constant(
  777. // ContactTrickEntry(
  778. // layout: .single,
  779. // ring: .iobcob,
  780. // primary: .glucose,
  781. // bottom: .trend,
  782. // fontSize: fontSize,
  783. // fontWeight: .medium
  784. // )
  785. // ),
  786. // state: .constant(ContactTrickState(
  787. // glucose: "6.8",
  788. // trend: "↗︎",
  789. // iob: 5.5,
  790. // iobText: "5.5",
  791. // cob: 25,
  792. // cobText: "25",
  793. // maxIOB: 10,
  794. // maxCOB: 120
  795. // ))
  796. // ).previewDisplayName("bg + trend + iobcob ring")
  797. }
  798. }
  799. static var previews: some View {
  800. Preview()
  801. }
  802. }