ImportExportSettingsView.swift 11 KB

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