ImportExportSettingsView.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // LoopFollow
  2. // ImportExportSettingsView.swift
  3. import AVFoundation
  4. import SwiftUI
  5. import UIKit
  6. struct ImportExportSettingsView: View {
  7. @StateObject private var viewModel = ImportExportSettingsViewModel()
  8. var body: some View {
  9. NavigationView {
  10. List {
  11. // MARK: - Import Section
  12. Section("Import Settings") {
  13. Button(action: {
  14. viewModel.isShowingQRCodeScanner = true
  15. }) {
  16. HStack {
  17. Image(systemName: "qrcode.viewfinder")
  18. .foregroundColor(.blue)
  19. Text("Scan QR Code to Import Settings")
  20. }
  21. }
  22. .buttonStyle(.plain)
  23. }
  24. // MARK: - Export Section
  25. Section("Export Settings To QR Code") {
  26. ForEach(ImportExportSettingsViewModel.ExportType.allCases, id: \.self) { exportType in
  27. Button(action: {
  28. if exportType == .alarms {
  29. viewModel.showAlarmSelection()
  30. } else {
  31. viewModel.exportType = exportType
  32. if let qrString = viewModel.generateQRCodeForExport() {
  33. viewModel.qrCodeString = qrString
  34. viewModel.isShowingQRCodeDisplay = true
  35. }
  36. }
  37. }) {
  38. HStack {
  39. Image(systemName: exportType.icon)
  40. .foregroundColor(.blue)
  41. Text("Export \(exportType.rawValue)")
  42. Spacer()
  43. Image(systemName: exportType == .alarms ? "list.bullet" : "qrcode")
  44. .foregroundColor(.secondary)
  45. }
  46. }
  47. .buttonStyle(.plain)
  48. }
  49. }
  50. // MARK: - Status Message
  51. if !viewModel.qrCodeErrorMessage.isEmpty {
  52. Section {
  53. let isSuccess = viewModel.qrCodeErrorMessage.contains("successfully") || viewModel.qrCodeErrorMessage.contains("Successfully imported")
  54. let displayText = isSuccess ? "✅ \(viewModel.qrCodeErrorMessage)" : viewModel.qrCodeErrorMessage
  55. Text(displayText)
  56. .foregroundColor(isSuccess ? .green : .red)
  57. .font(.caption)
  58. }
  59. }
  60. }
  61. .navigationTitle("Import/Export Settings")
  62. .navigationBarTitleDisplayMode(.inline)
  63. }
  64. .sheet(isPresented: $viewModel.isShowingQRCodeScanner) {
  65. SimpleQRCodeScannerView { result in
  66. viewModel.handleQRCodeScanResult(result)
  67. }
  68. }
  69. .sheet(isPresented: $viewModel.isShowingQRCodeDisplay) {
  70. NavigationView {
  71. VStack {
  72. if !viewModel.qrCodeString.isEmpty {
  73. QRCodeDisplayView(
  74. qrCodeString: viewModel.qrCodeString,
  75. size: CGSize(width: 300, height: 300)
  76. )
  77. .padding()
  78. Text("Scan this QR code with another LoopFollow app to import \(viewModel.exportType.rawValue.lowercased())")
  79. .font(.caption)
  80. .foregroundColor(.secondary)
  81. .multilineTextAlignment(.center)
  82. .padding(.horizontal, 20)
  83. } else {
  84. Text("Failed to generate QR code")
  85. .foregroundColor(.red)
  86. .padding()
  87. }
  88. }
  89. .navigationTitle("Export \(viewModel.exportType.rawValue)")
  90. .navigationBarTitleDisplayMode(.inline)
  91. .navigationBarItems(trailing: Button("Close") {
  92. viewModel.isShowingQRCodeDisplay = false
  93. })
  94. }
  95. }
  96. .sheet(isPresented: $viewModel.isShowingAlarmSelection) {
  97. AlarmSelectionView(
  98. exportedAlarmIds: viewModel.exportedAlarmIds,
  99. onConfirm: { selectedAlarms in
  100. viewModel.exportSelectedAlarms(selectedAlarms)
  101. },
  102. onCancel: {
  103. viewModel.cancelAlarmSelection()
  104. }
  105. )
  106. }
  107. .onDisappear {
  108. viewModel.resetExportedAlarms()
  109. }
  110. .sheet(isPresented: $viewModel.showImportConfirmation) {
  111. ImportConfirmationView(viewModel: viewModel)
  112. }
  113. }
  114. }
  115. struct ImportConfirmationView: View {
  116. @ObservedObject var viewModel: ImportExportSettingsViewModel
  117. var body: some View {
  118. NavigationView {
  119. VStack(spacing: 20) {
  120. // Header
  121. VStack(spacing: 8) {
  122. Image(systemName: "square.and.arrow.down")
  123. .font(.system(size: 50))
  124. .foregroundColor(.blue)
  125. Text("Import Settings")
  126. .font(.title2)
  127. .fontWeight(.semibold)
  128. Text("Review the settings that will be imported")
  129. .font(.subheadline)
  130. .foregroundColor(.secondary)
  131. .multilineTextAlignment(.center)
  132. }
  133. .padding(.top, 20)
  134. // Settings Preview
  135. if let preview = viewModel.importPreview {
  136. VStack(alignment: .leading, spacing: 16) {
  137. Text("Settings to Import")
  138. .font(.headline)
  139. .padding(.horizontal)
  140. VStack(spacing: 12) {
  141. if let url = preview.nightscoutURL, !url.isEmpty {
  142. SettingRowView(
  143. icon: "network",
  144. title: "Nightscout URL",
  145. value: url,
  146. color: .blue
  147. )
  148. }
  149. if let username = preview.dexcomUsername, !username.isEmpty {
  150. SettingRowView(
  151. icon: "person.circle",
  152. title: "Dexcom Username",
  153. value: username,
  154. color: .green
  155. )
  156. }
  157. if let remoteType = preview.remoteType, !remoteType.isEmpty, remoteType != "None" {
  158. SettingRowView(
  159. icon: "antenna.radiowaves.left.and.right",
  160. title: "Remote Type",
  161. value: remoteType,
  162. color: .orange
  163. )
  164. }
  165. if preview.alarmCount > 0 {
  166. SettingRowView(
  167. icon: "bell",
  168. title: "Alarms",
  169. value: "\(preview.alarmCount) alarm(s): \(preview.alarmNames.joined(separator: ", "))",
  170. color: .red
  171. )
  172. }
  173. }
  174. .padding(.horizontal)
  175. }
  176. }
  177. // Warning
  178. VStack(spacing: 8) {
  179. HStack {
  180. Image(systemName: "exclamationmark.triangle")
  181. .foregroundColor(.orange)
  182. Text("Warning")
  183. .fontWeight(.semibold)
  184. .foregroundColor(.orange)
  185. }
  186. Text("This will overwrite your current settings")
  187. .font(.subheadline)
  188. .foregroundColor(.secondary)
  189. .multilineTextAlignment(.center)
  190. }
  191. .padding(.horizontal)
  192. Spacer()
  193. // Action Buttons
  194. VStack(spacing: 12) {
  195. Button(action: {
  196. viewModel.confirmImport()
  197. }) {
  198. HStack {
  199. Image(systemName: "checkmark")
  200. Text("Import Settings")
  201. }
  202. .font(.headline)
  203. .foregroundColor(.white)
  204. .frame(maxWidth: .infinity)
  205. .padding()
  206. .background(Color.blue)
  207. .cornerRadius(12)
  208. }
  209. Button(action: {
  210. viewModel.cancelImport()
  211. }) {
  212. HStack {
  213. Image(systemName: "xmark")
  214. Text("Cancel")
  215. }
  216. .font(.headline)
  217. .foregroundColor(.primary)
  218. .frame(maxWidth: .infinity)
  219. .padding()
  220. .background(Color(.systemGray6))
  221. .cornerRadius(12)
  222. }
  223. }
  224. .padding(.horizontal)
  225. .padding(.bottom, 20)
  226. }
  227. .navigationBarHidden(true)
  228. }
  229. }
  230. }
  231. struct SettingRowView: View {
  232. let icon: String
  233. let title: String
  234. let value: String
  235. let color: Color
  236. var body: some View {
  237. HStack(spacing: 12) {
  238. Image(systemName: icon)
  239. .font(.title2)
  240. .foregroundColor(color)
  241. .frame(width: 30)
  242. VStack(alignment: .leading, spacing: 2) {
  243. Text(title)
  244. .font(.subheadline)
  245. .fontWeight(.medium)
  246. Text(value)
  247. .font(.caption)
  248. .foregroundColor(.secondary)
  249. .lineLimit(2)
  250. }
  251. Spacer()
  252. }
  253. .padding()
  254. .background(Color(.systemGray6))
  255. .cornerRadius(10)
  256. }
  257. }
  258. #Preview {
  259. ImportExportSettingsView()
  260. }