DatePickerRow.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. //
  2. // DatePickerRow.swift
  3. // LoopKitUI
  4. //
  5. // Created by Noah Brauner on 7/19/23.
  6. // Copyright © 2023 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. public struct DatePickerRow: View {
  10. @Binding var date: Date
  11. private var datePickerDate: Binding<Date> {
  12. Binding<Date>(
  13. get: { self.date },
  14. set: { validateDate($0) }
  15. )
  16. }
  17. @Binding var isFocused: Bool
  18. private let maximumDate: Date
  19. private let minimumDate: Date
  20. @State var incrementButtonEnabled = true
  21. @State var decrementButtonEnabled = true
  22. private let dateFormatter: DateFormatter = {
  23. let formatter = DateFormatter()
  24. formatter.timeStyle = .short
  25. formatter.dateStyle = .none
  26. return formatter
  27. }()
  28. private let relativeDateFormatter: DateFormatter = {
  29. let formatter = DateFormatter()
  30. formatter.doesRelativeDateFormatting = true
  31. formatter.timeStyle = .short
  32. formatter.dateStyle = .short
  33. return formatter
  34. }()
  35. private let timeStepSize: TimeInterval = .minutes(15)
  36. public init(date: Binding<Date>, isFocused: Binding<Bool>, minimumDate: Date, maximumDate: Date) {
  37. self._date = date
  38. self._isFocused = isFocused
  39. self.minimumDate = minimumDate
  40. self.maximumDate = maximumDate
  41. }
  42. public var body: some View {
  43. VStack(alignment: .leading, spacing: 0) {
  44. HStack {
  45. Text("Time")
  46. .foregroundColor(.primary)
  47. Spacer()
  48. Button(action: decrementTime) {
  49. Image(systemName: "minus.circle.fill")
  50. .foregroundColor(.accentColor)
  51. .font(.system(size: 24))
  52. .opacity(decrementButtonEnabled ? 1 : 0.7)
  53. }
  54. .disabled(!decrementButtonEnabled)
  55. let dateTextColor: Color = isFocused ? .accentColor : Color(UIColor.secondaryLabel)
  56. Text(dateString())
  57. .foregroundColor(dateTextColor)
  58. Button(action: incrementTime) {
  59. Image(systemName: "plus.circle.fill")
  60. .foregroundColor(.accentColor)
  61. .font(.system(size: 24))
  62. .opacity(incrementButtonEnabled ? 1 : 0.7)
  63. }
  64. .disabled(!incrementButtonEnabled)
  65. }
  66. if isFocused {
  67. DatePicker(selection: datePickerDate, in: minimumDate...maximumDate, label: { EmptyView() })
  68. .datePickerStyle(.wheel)
  69. .labelsHidden()
  70. .opacity(isFocused ? 1 : 0)
  71. }
  72. }
  73. .onAppear {
  74. checkButtonsEnabled()
  75. }
  76. .onTapGesture {
  77. rowTapped()
  78. }
  79. }
  80. private func checkButtonsEnabled() {
  81. let maxOrder = Calendar.current.compare(date, to: maximumDate, toGranularity: .minute)
  82. incrementButtonEnabled = maxOrder == .orderedAscending
  83. let minOrder = Calendar.current.compare(date, to: minimumDate, toGranularity: .minute)
  84. decrementButtonEnabled = minOrder == .orderedDescending
  85. }
  86. private func decrementTime() {
  87. let potentialDate = date.addingTimeInterval(-timeStepSize)
  88. if Calendar.current.compare(potentialDate, to: minimumDate, toGranularity: .minute) != .orderedAscending {
  89. date = potentialDate
  90. } else {
  91. date = minimumDate
  92. }
  93. checkButtonsEnabled()
  94. }
  95. private func incrementTime() {
  96. let potentialDate = date.addingTimeInterval(timeStepSize)
  97. if Calendar.current.compare(potentialDate, to: maximumDate, toGranularity: .minute) != .orderedDescending {
  98. date = potentialDate
  99. } else {
  100. date = maximumDate
  101. }
  102. checkButtonsEnabled()
  103. }
  104. private func validateDate(_ date: Date) {
  105. if date >= maximumDate {
  106. self.date = maximumDate
  107. }
  108. else if date <= minimumDate {
  109. self.date = minimumDate
  110. }
  111. else {
  112. self.date = date
  113. }
  114. checkButtonsEnabled()
  115. }
  116. private func dateString() -> String {
  117. if Calendar.current.isDateInToday(date) {
  118. return dateFormatter.string(from: date)
  119. } else {
  120. return relativeDateFormatter.string(from: date)
  121. }
  122. }
  123. private func rowTapped() {
  124. withAnimation {
  125. isFocused.toggle()
  126. }
  127. }
  128. }