Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions LoopFollow/Settings/ImportExport/ExportableSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -398,13 +436,15 @@ struct CombinedSettingsExport: Codable {
let dexcom: DexcomSettingsExport?
let remote: RemoteSettingsExport?
let alarms: AlarmSettingsExport?
let apns: APNSSettingsExport?
let exportType: String
let timestamp: Date

init(nightscout: NightscoutSettingsExport? = nil,
dexcom: DexcomSettingsExport? = nil,
remote: RemoteSettingsExport? = nil,
alarms: AlarmSettingsExport? = nil,
apns: APNSSettingsExport? = nil,
exportType: String)
{
version = "1.0"
Expand All @@ -413,6 +453,7 @@ struct CombinedSettingsExport: Codable {
self.dexcom = dexcom
self.remote = remote
self.alarms = alarms
self.apns = apns
self.exportType = exportType
timestamp = Date()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct ImportPreview {
let remoteType: String?
let alarmCount: Int
let alarmNames: [String]
let apnsKeyId: String?
}

class ImportExportSettingsViewModel: ObservableObject {
Expand Down Expand Up @@ -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 {
Expand All @@ -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"
}
}
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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: ", ")
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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")
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading