BolusConfirmationView.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import Combine
  2. import SwiftUI
  3. struct BolusConfirmationView: View {
  4. @EnvironmentObject var state: WatchStateModel
  5. @State var crownProgress: CGFloat = 100.0
  6. @State var progress: CGFloat = 0
  7. private let elementSize: CGFloat = 30
  8. @State var progressReturn: AnyCancellable?
  9. @State var done = false
  10. var body: some View {
  11. VStack {
  12. GeometryReader { geo in
  13. ZStack(alignment: .top) {
  14. RoundedRectangle(cornerRadius: elementSize / 2, style: .circular)
  15. .fill(.secondary)
  16. .frame(width: elementSize, height: geo.size.height)
  17. .opacity(0.2)
  18. RoundedRectangle(cornerRadius: elementSize / 2, style: .circular)
  19. .fill(Color.insulin)
  20. .frame(width: elementSize, height: elementSize + (geo.size.height - elementSize) * progress / 100)
  21. .opacity(0.2)
  22. Image(systemName: "arrow.right")
  23. .resizable()
  24. .frame(width: elementSize / 2, height: elementSize / 2)
  25. .foregroundColor(.primary)
  26. .position(x: geo.size.width - elementSize / 4, y: elementSize / 2)
  27. .transition(.opacity)
  28. if done {
  29. Image(systemName: "checkmark.circle.fill")
  30. .resizable()
  31. .foregroundColor(.loopGreen)
  32. .frame(width: elementSize, height: elementSize)
  33. .position(
  34. x: geo.size.width / 2,
  35. y: elementSize / 2 + ((geo.size.height - elementSize) * progress / 100)
  36. )
  37. } else {
  38. Image(systemName: "arrow.down.circle.fill")
  39. .resizable()
  40. .foregroundColor(.insulin)
  41. .frame(width: elementSize, height: elementSize)
  42. .position(
  43. x: geo.size.width / 2,
  44. y: elementSize / 2 + ((geo.size.height - elementSize) * progress / 100)
  45. )
  46. }
  47. }
  48. .frame(maxWidth: .infinity, maxHeight: .infinity)
  49. }
  50. .padding()
  51. Button {
  52. WKInterfaceDevice.current().play(.click)
  53. state.pendingBolus = nil
  54. state.isConfirmationBolusViewActive = false
  55. }
  56. label: {
  57. Text("Cancel")
  58. }
  59. }
  60. .focusable(true)
  61. .digitalCrownRotation(
  62. $crownProgress,
  63. from: 0.0,
  64. through: 100.0,
  65. by: 0.5,
  66. sensitivity: .high,
  67. isContinuous: false,
  68. isHapticFeedbackEnabled: true
  69. )
  70. .onChange(of: crownProgress) { _ in
  71. guard !done else { return }
  72. progressReturn?.cancel()
  73. progress = min(max(0, 100 - crownProgress), 100)
  74. if progress >= 100 {
  75. success()
  76. } else {
  77. progressReturn = Just(())
  78. .delay(for: 0.1, scheduler: RunLoop.main)
  79. .sink { _ in
  80. crownProgress = 100
  81. withAnimation {
  82. progress = 0
  83. }
  84. }
  85. }
  86. }
  87. }
  88. private func success() {
  89. WKInterfaceDevice.current().play(.success)
  90. withAnimation {
  91. done = true
  92. }
  93. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  94. state.enactBolus()
  95. }
  96. }
  97. }
  98. struct BolusConfirmationView_Previews: PreviewProvider {
  99. static var previews: some View {
  100. BolusConfirmationView(progress: 50, done: false).environmentObject(WatchStateModel())
  101. }
  102. }