WatchConfigGarminView.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import ConnectIQ
  2. import SwiftUI
  3. struct WatchConfigGarminView: View {
  4. @ObservedObject var state: WatchConfig.StateModel
  5. @State private var showDeviceList = false
  6. @State private var shouldDisplayHint: Bool = false
  7. @State var hintDetent = PresentationDetent.large
  8. @Environment(\.colorScheme) var colorScheme
  9. @Environment(AppState.self) var appState
  10. /// Handles deletion of devices from the device list
  11. private func onDelete(offsets: IndexSet) {
  12. state.devices.remove(atOffsets: offsets)
  13. state.deleteGarminDevice()
  14. }
  15. #if targetEnvironment(simulator)
  16. /// Adds a mock Garmin device for simulator UI testing
  17. private func addMockDevice() {
  18. let mockDevice = BaseGarminManager.MockIQDevice.createSimulated()
  19. state.devices.append(mockDevice)
  20. state.deleteGarminDevice()
  21. }
  22. #endif
  23. var body: some View {
  24. Group {
  25. if state.devices.isEmpty || showDeviceList {
  26. // No devices connected OR user wants to see device list - show device list/add view
  27. deviceListView
  28. } else {
  29. // Devices connected - go directly to configuration
  30. WatchConfigGarminAppConfigView(state: state)
  31. .navigationTitle("Garmin App Settings")
  32. .navigationBarTitleDisplayMode(.automatic)
  33. .navigationBarBackButtonHidden(true)
  34. .toolbar {
  35. ToolbarItem(placement: .navigationBarLeading) {
  36. Button(action: {
  37. showDeviceList = true
  38. }) {
  39. HStack {
  40. Image(systemName: "chevron.left")
  41. Text("Garmin Devices")
  42. }
  43. }
  44. }
  45. }
  46. }
  47. }
  48. .id(state.devices.count) // Force view refresh when device count changes
  49. .onChange(of: state.devices.count) { _, newValue in
  50. // If devices were deleted and now empty, ensure we show device list
  51. if newValue == 0 {
  52. showDeviceList = false
  53. }
  54. }
  55. }
  56. var deviceListView: some View {
  57. Form {
  58. #if targetEnvironment(simulator)
  59. // MARK: - Simulator Testing
  60. Section(
  61. header: Text("Simulator Testing"),
  62. content: {
  63. VStack {
  64. if state.devices.isEmpty {
  65. Button {
  66. // Add a mock device for UI testing
  67. addMockDevice()
  68. } label: {
  69. Text("Add Mock Garmin Watch")
  70. .font(.title3)
  71. }
  72. .frame(maxWidth: .infinity, alignment: .center)
  73. .buttonStyle(.bordered)
  74. } else {
  75. Button {
  76. state.devices.removeAll()
  77. state.deleteGarminDevice()
  78. } label: {
  79. Text("Remove All Devices")
  80. .font(.title3)
  81. }
  82. .frame(maxWidth: .infinity, alignment: .center)
  83. .buttonStyle(.bordered)
  84. .tint(.red)
  85. }
  86. Text("Simulator only - for testing UI workflow")
  87. .font(.caption)
  88. .foregroundColor(.orange)
  89. .padding(.top, 5)
  90. }.padding(.vertical)
  91. }
  92. ).listRowBackground(Color.orange.opacity(0.2))
  93. #endif
  94. // MARK: - Device Configuration Section
  95. Section(
  96. header: Text("Garmin Configuration"),
  97. content: {
  98. VStack {
  99. Button {
  100. state.selectGarminDevices()
  101. } label: {
  102. Text("Add Device")
  103. .font(.title3)
  104. }
  105. .frame(maxWidth: .infinity, alignment: .center)
  106. .buttonStyle(.bordered)
  107. HStack(alignment: .center) {
  108. Text(
  109. "Add a Garmin Device to Trio."
  110. )
  111. .font(.footnote)
  112. .foregroundColor(.secondary)
  113. .lineLimit(nil)
  114. Spacer()
  115. Button(
  116. action: {
  117. shouldDisplayHint.toggle()
  118. },
  119. label: {
  120. HStack {
  121. Image(systemName: "questionmark.circle")
  122. }
  123. }
  124. ).buttonStyle(BorderlessButtonStyle())
  125. }.padding(.top)
  126. }.padding(.vertical)
  127. }
  128. ).listRowBackground(Color.chart)
  129. // MARK: - Device List Section
  130. if !state.devices.isEmpty {
  131. Section(
  132. header: Text("Connected Devices"),
  133. content: {
  134. List {
  135. ForEach(state.devices, id: \.uuid) { device in
  136. Text(device.friendlyName)
  137. }
  138. .onDelete(perform: onDelete)
  139. }
  140. }
  141. ).listRowBackground(Color.chart)
  142. // MARK: - App Settings Navigation Section
  143. Section(
  144. header: Text("Device App Settings"),
  145. content: {
  146. Button(action: {
  147. showDeviceList = false
  148. }) {
  149. HStack {
  150. Text("Configure Device Apps")
  151. Spacer()
  152. Image(systemName: "chevron.right")
  153. .font(.caption)
  154. .foregroundColor(.secondary)
  155. }
  156. }
  157. .foregroundColor(.primary)
  158. }
  159. ).listRowBackground(Color.chart)
  160. }
  161. }
  162. .listSectionSpacing(sectionSpacing)
  163. .navigationTitle("Garmin Devices")
  164. .navigationBarTitleDisplayMode(.automatic)
  165. .scrollContentBackground(.hidden)
  166. .background(appState.trioBackgroundColor(for: colorScheme))
  167. .sheet(isPresented: $shouldDisplayHint) {
  168. SettingInputHintView(
  169. hintDetent: $hintDetent,
  170. shouldDisplayHint: $shouldDisplayHint,
  171. hintLabel: "Add Device",
  172. hintText: Text(
  173. "Add Garmin Device to Trio. This happens via Garmin Connect. If you have multiple phones with Garmin Connect and the same Garmin device, you will run into connectivity issue between watch and phone depending of proximity of the phones, which might also affect your watchface function."
  174. ),
  175. sheetTitle: String(localized: "Help", comment: "Help sheet title")
  176. )
  177. }
  178. }
  179. }