CarbQuantityRow.swift 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. //
  2. // CarbQuantityRow.swift
  3. // LoopKitUI
  4. //
  5. // Created by Noah Brauner on 7/20/23.
  6. // Copyright © 2023 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. import LoopKit
  10. import HealthKit
  11. public struct CarbQuantityRow: View {
  12. @Binding private var quantity: Double?
  13. @Binding private var isFocused: Bool
  14. private let title: String
  15. private let preferredCarbUnit: HKUnit
  16. @State private var carbInput: String = ""
  17. private let formatter: NumberFormatter = {
  18. let formatter = NumberFormatter()
  19. formatter.numberStyle = .decimal
  20. formatter.maximumFractionDigits = 1
  21. return formatter
  22. }()
  23. public init(quantity: Binding<Double?>, isFocused: Binding<Bool>, title: String, preferredCarbUnit: HKUnit = .gram()) {
  24. self._quantity = quantity
  25. self._isFocused = isFocused
  26. self.title = title
  27. self.preferredCarbUnit = preferredCarbUnit
  28. }
  29. public var body: some View {
  30. HStack(spacing: 2) {
  31. Text(title)
  32. .foregroundColor(.primary)
  33. .frame(maxWidth: .infinity, alignment: .leading)
  34. RowTextField(text: $carbInput, isFocused: $isFocused, maxLength: 5) {
  35. $0.textAlignment = .right
  36. $0.keyboardType = .decimalPad
  37. $0.placeholder = "0"
  38. $0.font = .preferredFont(forTextStyle: .body)
  39. }
  40. .onTapGesture {
  41. // so that row does not lose focus on cursor move
  42. if !isFocused {
  43. rowTapped()
  44. }
  45. }
  46. carbUnitsLabel
  47. }
  48. .accessibilityElement(children: .combine)
  49. .onChange(of: carbInput) { newValue in
  50. updateQuantity(with: newValue)
  51. }
  52. .onChange(of: quantity) { newQuantity in
  53. updateCarbInput(with: newQuantity)
  54. }
  55. .onAppear {
  56. updateCarbInput(with: quantity)
  57. }
  58. .onTapGesture {
  59. rowTapped()
  60. }
  61. }
  62. private var carbUnitsLabel: some View {
  63. Text(QuantityFormatter(for: preferredCarbUnit).localizedUnitStringWithPlurality())
  64. .foregroundColor(Color(.secondaryLabel))
  65. }
  66. // Update quantity based on text field input
  67. private func updateQuantity(with input: String) {
  68. let filtered = input.filter { "0123456789.".contains($0) }
  69. if filtered != input {
  70. self.carbInput = filtered
  71. }
  72. if let doubleValue = Double(filtered) {
  73. quantity = doubleValue
  74. } else {
  75. quantity = nil
  76. }
  77. }
  78. // Update text field input based on quantity
  79. private func updateCarbInput(with newQuantity: Double?) {
  80. if let value = newQuantity {
  81. carbInput = formatter.string(from: NSNumber(value: value)) ?? ""
  82. } else {
  83. carbInput = ""
  84. }
  85. }
  86. private func rowTapped() {
  87. withAnimation {
  88. isFocused.toggle()
  89. }
  90. }
  91. }