ImportExportSettingsView.swift 11 KB

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