BolusConfirmationView.swift 4.3 KB

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