Procházet zdrojové kódy

Add new section to export APNS setting (#714)

Daniel Mini Johansson před 1 týdnem
rodič
revize
eb15e4c648

+ 41 - 0
LoopFollow/Settings/ImportExport/ExportableSettings.swift

@@ -389,6 +389,44 @@ struct RemoteSettingsExport: Codable {
     }
 }
 
+// MARK: - APNS Settings Export
+
+struct APNSSettingsExport: Codable {
+    let version: String
+    let keyId: String
+    let apnsKey: String
+
+    static func fromCurrentStorage() -> APNSSettingsExport {
+        let storage = Storage.shared
+        return APNSSettingsExport(
+            version: AppVersionManager().version(),
+            keyId: storage.lfKeyId.value,
+            apnsKey: storage.lfApnsKey.value
+        )
+    }
+
+    func applyToStorage() {
+        let storage = Storage.shared
+        storage.lfKeyId.value = keyId
+
+        let apnsService = LoopAPNSService()
+        storage.lfApnsKey.value = apnsService.validateAndFixAPNSKey(apnsKey)
+    }
+
+    func encodeToJSON() -> String? {
+        do {
+            let data = try JSONEncoder().encode(self)
+            return String(data: data, encoding: .utf8)
+        } catch {
+            return nil
+        }
+    }
+
+    func hasValidSettings() -> Bool {
+        APNsCredentialValidator.isFullyConfigured(keyId: keyId, apnsKey: apnsKey)
+    }
+}
+
 // MARK: - Combined Settings Export
 
 struct CombinedSettingsExport: Codable {
@@ -398,6 +436,7 @@ struct CombinedSettingsExport: Codable {
     let dexcom: DexcomSettingsExport?
     let remote: RemoteSettingsExport?
     let alarms: AlarmSettingsExport?
+    let apns: APNSSettingsExport?
     let exportType: String
     let timestamp: Date
 
@@ -405,6 +444,7 @@ struct CombinedSettingsExport: Codable {
          dexcom: DexcomSettingsExport? = nil,
          remote: RemoteSettingsExport? = nil,
          alarms: AlarmSettingsExport? = nil,
+         apns: APNSSettingsExport? = nil,
          exportType: String)
     {
         version = "1.0"
@@ -413,6 +453,7 @@ struct CombinedSettingsExport: Codable {
         self.dexcom = dexcom
         self.remote = remote
         self.alarms = alarms
+        self.apns = apns
         self.exportType = exportType
         timestamp = Date()
     }

+ 17 - 1
LoopFollow/Settings/ImportExport/ImportExportSettingsView.swift

@@ -83,7 +83,7 @@ struct ImportExportSettingsView: View {
                         )
                         .padding()
 
-                        Text("Scan this QR code with another LoopFollow app to import \(viewModel.exportType.rawValue.lowercased())")
+                        Text("Scan this QR code with another LoopFollow app to import \(viewModel.exportType.importDescription)")
                             .font(.caption)
                             .foregroundColor(.secondary)
                             .multilineTextAlignment(.center)
@@ -187,6 +187,15 @@ struct ImportConfirmationView: View {
                                     color: .red
                                 )
                             }
+
+                            if let apnsKeyId = preview.apnsKeyId, !apnsKeyId.isEmpty {
+                                SettingRowView(
+                                    icon: "key.horizontal",
+                                    title: "APNS Key ID",
+                                    value: apnsKeyId,
+                                    color: .purple
+                                )
+                            }
                         }
                         .padding(.horizontal)
                     }
@@ -206,6 +215,13 @@ struct ImportConfirmationView: View {
                         .font(.subheadline)
                         .foregroundColor(.secondary)
                         .multilineTextAlignment(.center)
+
+                    if let apnsKeyId = viewModel.importPreview?.apnsKeyId, !apnsKeyId.isEmpty {
+                        Text("APNS warning: importing APNS settings will overwrite your existing APNS Key ID and APNS Key.")
+                            .font(.caption)
+                            .foregroundColor(.orange)
+                            .multilineTextAlignment(.center)
+                    }
                 }
                 .padding(.horizontal)
 

+ 40 - 4
LoopFollow/Settings/ImportExport/ImportExportSettingsViewModel.swift

@@ -10,6 +10,7 @@ struct ImportPreview {
     let remoteType: String?
     let alarmCount: Int
     let alarmNames: [String]
+    let apnsKeyId: String?
 }
 
 class ImportExportSettingsViewModel: ObservableObject {
@@ -38,6 +39,7 @@ class ImportExportSettingsViewModel: ObservableObject {
         case nightscout = "Nightscout Settings"
         case dexcom = "Dexcom Share Settings"
         case remote = "Remote Settings"
+        case apns = "APNS Settings"
         case alarms = "Alarm Settings"
 
         var icon: String {
@@ -46,6 +48,17 @@ class ImportExportSettingsViewModel: ObservableObject {
             case .dexcom: return "network"
             case .remote: return "antenna.radiowaves.left.and.right"
             case .alarms: return "bell"
+            case .apns: return "key.horizontal"
+            }
+        }
+
+        var importDescription: String {
+            switch self {
+            case .nightscout: return "Nightscout Settings"
+            case .dexcom: return "Dexcom Share Settings"
+            case .remote: return "Remote Settings"
+            case .alarms: return "Alarm Settings"
+            case .apns: return "APNS Settings"
             }
         }
     }
@@ -91,7 +104,7 @@ class ImportExportSettingsViewModel: ObservableObject {
                 return
             }
 
-            LogManager.shared.log(category: .general, message: "QR code decoded successfully. Components: nightscout=\(settings.nightscout != nil), remote=\(settings.remote != nil), alarms=\(settings.alarms != nil)")
+            LogManager.shared.log(category: .general, message: "QR code decoded successfully. Components: nightscout=\(settings.nightscout != nil), remote=\(settings.remote != nil), alarms=\(settings.alarms != nil), apns=\(settings.apns != nil)")
 
             // Check version compatibility
             let currentVersion = AppVersionManager().version()
@@ -163,6 +176,16 @@ class ImportExportSettingsViewModel: ObservableObject {
             LogManager.shared.log(category: .general, message: "Alarm settings imported from \(source) (version: \(alarms.version), \(alarms.alarms.count) alarms)")
         }
 
+        if let apns = settings.apns {
+            if apns.hasValidSettings() {
+                apns.applyToStorage()
+                importedComponents.append("APNS settings")
+                LogManager.shared.log(category: .general, message: "APNS settings imported from \(source) (version: \(apns.version), keyId: \(apns.keyId))")
+            } else {
+                throw NSError(domain: "SettingsImport", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid APNS settings in \(source)"])
+            }
+        }
+
         // Update the success message with what was imported
         if !importedComponents.isEmpty {
             let componentsList = importedComponents.joined(separator: ", ")
@@ -220,6 +243,16 @@ class ImportExportSettingsViewModel: ObservableObject {
                 dexcom: dexcomSettings,
                 exportType: exportType.rawValue
             )
+        case .apns:
+            let apnsSettings = APNSSettingsExport.fromCurrentStorage()
+            if !apnsSettings.hasValidSettings() {
+                qrCodeErrorMessage = "Please configure your APNS settings first (valid APNS Key ID and APNS Key)"
+                return nil
+            }
+            settings = CombinedSettingsExport(
+                apns: apnsSettings,
+                exportType: exportType.rawValue
+            )
         }
 
         return settings?.encodeToJSON()
@@ -257,14 +290,16 @@ class ImportExportSettingsViewModel: ObservableObject {
         let remoteType = settings.remote?.remoteType != .none ? settings.remote?.remoteType.rawValue : nil
         let alarmCount = settings.alarms?.alarms.count ?? 0
         let alarmNames = settings.alarms?.alarms.map { $0.name } ?? []
+        let apnsKeyId = settings.apns?.keyId.isEmpty == false ? settings.apns?.keyId : nil
 
         // Check if any settings are actually present
         let hasAnySettings = (nightscoutURL != nil && !nightscoutURL!.isEmpty) ||
             (dexcomUsername != nil && !dexcomUsername!.isEmpty) ||
             (remoteType != nil && !remoteType!.isEmpty && remoteType != "None") ||
-            alarmCount > 0
+            alarmCount > 0 ||
+            (apnsKeyId != nil && !apnsKeyId!.isEmpty)
 
-        LogManager.shared.log(category: .general, message: "Import preview check - nightscoutURL: \(nightscoutURL.map { LogRedactor.url($0) } ?? "nil"), dexcomUsername: \(dexcomUsername.map { LogRedactor.username($0) } ?? "nil"), remoteType: \(remoteType ?? "nil"), alarmCount: \(alarmCount), hasAnySettings: \(hasAnySettings)")
+        LogManager.shared.log(category: .general, message: "Import preview check - nightscoutURL: \(nightscoutURL.map { LogRedactor.url($0) } ?? "nil"), dexcomUsername: \(dexcomUsername.map { LogRedactor.username($0) } ?? "nil"), remoteType: \(remoteType ?? "nil"), alarmCount: \(alarmCount), apnsKeyIdPresent: \(apnsKeyId != nil), hasAnySettings: \(hasAnySettings)")
 
         if hasAnySettings {
             LogManager.shared.log(category: .general, message: "Creating import preview with settings")
@@ -273,7 +308,8 @@ class ImportExportSettingsViewModel: ObservableObject {
                 dexcomUsername: dexcomUsername,
                 remoteType: remoteType,
                 alarmCount: alarmCount,
-                alarmNames: alarmNames
+                alarmNames: alarmNames,
+                apnsKeyId: apnsKeyId
             )
             LogManager.shared.log(category: .general, message: "Created importPreview - nightscoutURL: \(importPreview?.nightscoutURL.map { LogRedactor.url($0) } ?? "nil"), remoteType: \(importPreview?.remoteType ?? "nil"), alarmCount: \(importPreview?.alarmCount ?? 0)")
             showImportConfirmation = true

+ 9 - 0
LoopFollow/Settings/ImportExport/SettingsMigrationManager.swift

@@ -61,6 +61,15 @@ class SettingsMigrationManager {
             )
         }
 
+        // Try to decode as APNSSettingsExport
+        if let apnsSettings = try? JSONDecoder().decode(APNSSettingsExport.self, from: data) {
+            LogManager.shared.log(category: .general, message: "Successfully decoded as APNSSettingsExport", isDebug: true)
+            return CombinedSettingsExport(
+                apns: apnsSettings,
+                exportType: "APNS Settings"
+            )
+        }
+
         LogManager.shared.log(category: .general, message: "Failed to decode as any known component", isDebug: true)
         return nil
     }