TabCustomizationModal.swift 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // LoopFollow
  2. // TabCustomizationModal.swift
  3. import SwiftUI
  4. struct TabCustomizationModal: View {
  5. @Binding var isPresented: Bool
  6. let onApply: () -> Void
  7. // Local state for editing
  8. @State private var alarmsPosition: TabPosition
  9. @State private var remotePosition: TabPosition
  10. @State private var nightscoutPosition: TabPosition
  11. @State private var hasChanges = false
  12. // Store original values to detect changes
  13. private let originalAlarmsPosition: TabPosition
  14. private let originalRemotePosition: TabPosition
  15. private let originalNightscoutPosition: TabPosition
  16. init(isPresented: Binding<Bool>, onApply: @escaping () -> Void) {
  17. _isPresented = isPresented
  18. self.onApply = onApply
  19. // Initialize with current values
  20. let currentAlarms = Storage.shared.alarmsPosition.value
  21. let currentRemote = Storage.shared.remotePosition.value
  22. let currentNightscout = Storage.shared.nightscoutPosition.value
  23. _alarmsPosition = State(initialValue: currentAlarms)
  24. _remotePosition = State(initialValue: currentRemote)
  25. _nightscoutPosition = State(initialValue: currentNightscout)
  26. originalAlarmsPosition = currentAlarms
  27. originalRemotePosition = currentRemote
  28. originalNightscoutPosition = currentNightscout
  29. }
  30. var body: some View {
  31. NavigationView {
  32. Form {
  33. Section("Tab Positions") {
  34. TabPositionRow(
  35. title: "Alarms",
  36. icon: "alarm",
  37. position: $alarmsPosition,
  38. otherPositions: [remotePosition, nightscoutPosition]
  39. )
  40. .onChange(of: alarmsPosition) { _ in checkForChanges() }
  41. TabPositionRow(
  42. title: "Remote",
  43. icon: "antenna.radiowaves.left.and.right",
  44. position: $remotePosition,
  45. otherPositions: [alarmsPosition, nightscoutPosition]
  46. )
  47. .onChange(of: remotePosition) { _ in checkForChanges() }
  48. TabPositionRow(
  49. title: "Nightscout",
  50. icon: "safari",
  51. position: $nightscoutPosition,
  52. otherPositions: [alarmsPosition, remotePosition]
  53. )
  54. .onChange(of: nightscoutPosition) { _ in checkForChanges() }
  55. }
  56. Section {
  57. Text("• Tab 2 and Tab 4 can each hold one item")
  58. .font(.caption)
  59. .foregroundColor(.secondary)
  60. Text("• Items in 'More Menu' appear under the last tab")
  61. .font(.caption)
  62. .foregroundColor(.secondary)
  63. Text("• Hidden items are not accessible")
  64. .font(.caption)
  65. .foregroundColor(.secondary)
  66. }
  67. if hasChanges {
  68. Section {
  69. Text("Changes will be applied when you tap 'Apply'")
  70. .font(.caption)
  71. .foregroundColor(.orange)
  72. }
  73. }
  74. }
  75. .navigationTitle("Tab Settings")
  76. .navigationBarTitleDisplayMode(.inline)
  77. .toolbar {
  78. ToolbarItem(placement: .navigationBarLeading) {
  79. Button("Cancel") {
  80. isPresented = false
  81. }
  82. }
  83. ToolbarItem(placement: .navigationBarTrailing) {
  84. Button("Apply") {
  85. applyChanges()
  86. }
  87. .fontWeight(.semibold)
  88. .disabled(!hasChanges)
  89. }
  90. }
  91. }
  92. .preferredColorScheme(Storage.shared.forceDarkMode.value ? .dark : nil)
  93. }
  94. private func checkForChanges() {
  95. hasChanges = alarmsPosition != originalAlarmsPosition ||
  96. remotePosition != originalRemotePosition ||
  97. nightscoutPosition != originalNightscoutPosition
  98. }
  99. private func applyChanges() {
  100. // Save the new positions
  101. Storage.shared.alarmsPosition.value = alarmsPosition
  102. Storage.shared.remotePosition.value = remotePosition
  103. Storage.shared.nightscoutPosition.value = nightscoutPosition
  104. // Dismiss the modal
  105. isPresented = false
  106. // Call the completion handler after a small delay to ensure modal is dismissed
  107. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  108. onApply()
  109. }
  110. }
  111. }
  112. struct TabPositionRow: View {
  113. let title: String
  114. let icon: String
  115. @Binding var position: TabPosition
  116. let otherPositions: [TabPosition]
  117. var availablePositions: [TabPosition] {
  118. TabPosition.allCases.filter { tabPosition in
  119. // Always allow current position and disabled/more
  120. if tabPosition == position || tabPosition == .more || tabPosition == .disabled {
  121. return true
  122. }
  123. // Otherwise, only allow if not taken by another position
  124. return !otherPositions.contains(tabPosition)
  125. }
  126. }
  127. var body: some View {
  128. HStack {
  129. Image(systemName: icon)
  130. .frame(width: 30)
  131. .foregroundColor(.accentColor)
  132. Text(title)
  133. Spacer()
  134. Picker(title, selection: $position) {
  135. ForEach(availablePositions, id: \.self) { pos in
  136. Text(pos.displayName).tag(pos)
  137. }
  138. }
  139. .pickerStyle(.menu)
  140. .labelsHidden()
  141. }
  142. }
  143. }