Pārlūkot izejas kodu

Reset mostly to dev so we can migrate one file at a time

Sam King 1 gadu atpakaļ
vecāks
revīzija
691caa365f

+ 0 - 33
Model/Helper/CarbEntryStored+helper.swift

@@ -70,39 +70,6 @@ extension CarbEntryStored {
     }
 }
 
-// MARK: - CarbEntryDTO and Conformance to ImportableDTO
-
-/// Data Transfer Object for Carb entries.
-struct CarbEntryDTO: Decodable, ImportableDTO {
-    var id: UUID?
-    var carbs: Double
-    var date: Date?
-    var fat: Double?
-    var protein: Double?
-    var isFPU: Bool?
-    var note: String?
-    var enteredBy: String?
-
-    // Conformance to ImportableDTO
-    typealias ManagedObject = CarbEntryStored
-
-    /// Stores the DTO in Core Data by mapping it to the corresponding managed object.
-    func store(in context: NSManagedObjectContext) -> CarbEntryStored {
-        let carbEntry = CarbEntryStored(context: context)
-        carbEntry.id = id ?? UUID()
-        carbEntry.carbs = carbs
-        carbEntry.date = date ?? Date()
-        carbEntry.fat = fat ?? 0.0
-        carbEntry.protein = protein ?? 0.0
-        carbEntry.isFPU = isFPU ?? false
-        carbEntry.note = note
-        carbEntry.isUploadedToNS = true
-        carbEntry.isUploadedToHealth = true
-        carbEntry.isUploadedToTidepool = true
-        return carbEntry
-    }
-}
-
 extension CarbEntryStored: Encodable {
     enum CodingKeys: String, CodingKey {
         case actualDate

+ 0 - 115
Model/Helper/Determination+helper.swift

@@ -89,118 +89,3 @@ extension NSPredicate {
         return NSPredicate(format: "deliverAt >= %@", date as NSDate)
     }
 }
-
-// MARK: - DeterminationDTO and Conformance to ImportableDTO
-
-/// Data Transfer Object for the enacted.json.
-struct DeterminationDTO: Decodable, ImportableDTO {
-    let threshold: Decimal?
-    let timestamp: String?
-    let insulinForManualBolus: Decimal?
-    let sensitivityRatio: Decimal?
-    let predictions: Predictions?
-    let received: Bool?
-    let currentTarget: Decimal?
-    let expectedDelta: Decimal?
-    let cob: Int?
-    let minDelta: Decimal?
-    let bg: Decimal?
-    let manualBolusErrorString: Decimal?
-    let eventualBG: Decimal?
-    let isf: Decimal?
-    let rate: Decimal?
-    let duration: Decimal?
-    let temp: String?
-    let insulinReq: Decimal?
-    let deliverAt: String?
-    let reason: String?
-    let iob: Decimal?
-    let reservoir: Decimal?
-
-    enum CodingKeys: String, CodingKey {
-        case threshold
-        case timestamp
-        case insulinForManualBolus
-        case sensitivityRatio
-        case predictions = "predBGs"
-        case received = "recieved"
-        case currentTarget = "current_target"
-        case expectedDelta
-        case cob = "COB"
-        case minDelta
-        case bg
-        case manualBolusErrorString
-        case eventualBG
-        case isf = "ISF"
-        case rate
-        case duration
-        case temp
-        case insulinReq
-        case deliverAt
-        case reason
-        case iob = "IOB"
-        case reservoir
-    }
-
-    // Conformance to ImportableDTO
-    typealias ManagedObject = OrefDetermination
-
-    /// Stores the DTO in Core Data by mapping it to the corresponding managed object.
-    func store(in context: NSManagedObjectContext) -> OrefDetermination {
-        let determinationEntity = OrefDetermination(context: context)
-        let dateFormatter = ISO8601DateFormatter()
-
-        determinationEntity.timestamp = timestamp.flatMap { dateFormatter.date(from: $0) }
-        determinationEntity.deliverAt = deliverAt.flatMap { dateFormatter.date(from: $0) }
-        determinationEntity.cob = cob.map { Int16($0) } ?? 0
-        determinationEntity.temp = temp
-        determinationEntity.iob = iob.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.minDelta = minDelta.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.expectedDelta = expectedDelta.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.rate = rate.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.reason = reason
-        determinationEntity.reservoir = reservoir.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.duration = duration.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.currentTarget = currentTarget.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.insulinForManualBolus = insulinForManualBolus.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.sensitivityRatio = sensitivityRatio.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.threshold = threshold.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.eventualBG = eventualBG.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.received = received ?? false
-        determinationEntity.insulinReq = insulinReq.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.insulinSensitivity = isf.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.manualBolusErrorString = manualBolusErrorString.map { NSDecimalNumber(decimal: $0) }
-        determinationEntity.glucose = bg.map { NSDecimalNumber(decimal: $0) }
-
-        if let predictionData = predictions {
-            var forecasts = Set<Forecast>()
-
-            if let iobPredictions = predictionData.iob {
-                forecasts.insert(createForecast(context: context, type: "IOB", values: iobPredictions))
-            }
-            if let ztPredictions = predictionData.zt {
-                forecasts.insert(createForecast(context: context, type: "ZT", values: ztPredictions))
-            }
-            if let uamPredictions = predictionData.uam {
-                forecasts.insert(createForecast(context: context, type: "UAM", values: uamPredictions))
-            }
-
-            determinationEntity.forecasts = forecasts
-        }
-
-        return determinationEntity
-    }
-
-    private func createForecast(context: NSManagedObjectContext, type: String, values: [Int]) -> Forecast {
-        let forecast = Forecast(context: context)
-        forecast.type = type
-        forecast.date = Date()
-        forecast.forecastValues = Set(values.enumerated().map { index, value in
-            let forecastValue = ForecastValue(context: context)
-            forecastValue.index = Int32(index)
-            forecastValue.value = Int32(value)
-            return forecastValue
-        })
-        return forecast
-    }
-}

+ 0 - 50
Model/Helper/GlucoseStored+helper.swift

@@ -132,56 +132,6 @@ extension NSPredicate {
     }
 }
 
-struct GlucoseEntryDTO: Decodable, ImportableDTO {
-    var id: UUID?
-    var date: Date?
-    var glucose: Int
-    var direction: String?
-    var isManual: Bool?
-
-    // Custom initializer to handle numeric dates
-    init(from decoder: Decoder) throws {
-        let container = try decoder.container(keyedBy: CodingKeys.self)
-        id = try container.decodeIfPresent(UUID.self, forKey: .id)
-        glucose = try container.decode(Int.self, forKey: .glucose)
-        direction = try container.decodeIfPresent(String.self, forKey: .direction)
-        isManual = try container.decodeIfPresent(Bool.self, forKey: .isManual) ?? false
-
-        // Handle numeric date
-        if let timestamp = try? container.decode(Double.self, forKey: .date) {
-            // Assuming the timestamp is in milliseconds
-            date = Date(timeIntervalSince1970: timestamp / 1000)
-        } else {
-            date = nil
-        }
-    }
-
-    enum CodingKeys: String, CodingKey {
-        case id
-        case date
-        case glucose
-        case direction
-        case isManual
-    }
-
-    // Conformance to ImportableDTO
-    typealias ManagedObject = GlucoseStored
-
-    func store(in context: NSManagedObjectContext) -> GlucoseStored {
-        let glucoseEntry = GlucoseStored(context: context)
-        glucoseEntry.id = id ?? UUID()
-        glucoseEntry.date = date ?? Date()
-        glucoseEntry.glucose = Int16(glucose)
-        glucoseEntry.direction = direction
-        glucoseEntry.isManual = isManual ?? false
-        glucoseEntry.isUploadedToNS = true
-        glucoseEntry.isUploadedToHealth = true
-        glucoseEntry.isUploadedToTidepool = true
-
-        return glucoseEntry
-    }
-}
-
 extension GlucoseStored: Encodable {
     enum CodingKeys: String, CodingKey {
         case date

+ 33 - 166
Model/Helper/PumpEvent+helper.swift

@@ -128,178 +128,15 @@ extension NSPredicate {
     }
 }
 
-// MARK: - PumpEventDTO and Conformance to ImportableDTO
-
-enum PumpEventDTO: Encodable, Decodable, ImportableDTO {
-    case bolus(BolusDTO)
-    case tempBasal(TempBasalDTO)
-    case tempBasalDuration(TempBasalDurationDTO)
-    case suspend(SuspendDTO)
-    case resume(ResumeDTO)
-    case rewind(RewindDTO)
-    case prime(PrimeDTO)
-    case unknown(String) // Catch-all for unknown types
-
-    func encode(to encoder: Encoder) throws {
-        switch self {
-        case let .bolus(bolus):
-            try bolus.encode(to: encoder)
-        case let .tempBasal(tempBasal):
-            try tempBasal.encode(to: encoder)
-        case let .tempBasalDuration(tempBasalDuration):
-            try tempBasalDuration.encode(to: encoder)
-        case let .suspend(suspend):
-            try suspend.encode(to: encoder)
-        case let .resume(resume):
-            try resume.encode(to: encoder)
-        case let .rewind(rewind):
-            try rewind.encode(to: encoder)
-        case let .prime(prime):
-            try prime.encode(to: encoder)
-        case let .unknown(type):
-            debugPrint("⚠️ Skipping unknown type during encoding: \(type)")
-        }
-    }
-
-    private enum CodingKeys: String, CodingKey {
-        case _type
-    }
-
-    init(from decoder: Decoder) throws {
-        let container = try decoder.container(keyedBy: CodingKeys.self)
-
-        // Attempt to decode `_type` key
-        guard let type = try? container.decode(String.self, forKey: ._type) else {
-            debugPrint("⚠️ Missing _type in JSON entry.")
-            self = .unknown("missing_type")
-            return
-        }
-
-        let singleValueContainer = try decoder.singleValueContainer()
-
-        switch type {
-        case "Bolus":
-            let bolusDTO = try singleValueContainer.decode(BolusDTO.self)
-            self = .bolus(bolusDTO)
-        case "TempBasal":
-            let tempBasalDTO = try singleValueContainer.decode(TempBasalDTO.self)
-            self = .tempBasal(tempBasalDTO)
-        case "TempBasalDuration":
-            let tempBasalDurationDTO = try singleValueContainer.decode(TempBasalDurationDTO.self)
-            self = .tempBasalDuration(tempBasalDurationDTO)
-        case "PumpSuspend":
-            let SuspendDTO = try singleValueContainer.decode(SuspendDTO.self)
-            self = .suspend(SuspendDTO)
-        case "PumpResume":
-            let ResumeDTO = try singleValueContainer.decode(ResumeDTO.self)
-            self = .resume(ResumeDTO)
-        default:
-            debugPrint("⚠️ Unknown _type value: \(type)")
-            self = .unknown(type)
-        }
-    }
-
-    // Conformance to ImportableDTO
-    typealias ManagedObject = PumpEventStored
-
-    func store(in context: NSManagedObjectContext) -> PumpEventStored {
-        let pumpEvent = PumpEventStored(context: context)
-        pumpEvent.isUploadedToNS = true
-        pumpEvent.isUploadedToHealth = true
-        pumpEvent.isUploadedToTidepool = true
-
-        switch self {
-        case let .bolus(bolusDTO):
-            pumpEvent.id = bolusDTO.id
-            pumpEvent.timestamp = ISO8601DateFormatter().date(from: bolusDTO.timestamp)
-            pumpEvent.type = bolusDTO._type
-
-            let bolus = BolusStored(context: context)
-            bolus.amount = NSDecimalNumber(value: bolusDTO.amount)
-            bolus.isExternal = bolusDTO.isExternal
-            bolus.isSMB = bolusDTO.isSMB
-            pumpEvent.bolus = bolus
-
-            return pumpEvent
-
-        case let .tempBasal(tempBasalDTO):
-            pumpEvent.id = tempBasalDTO.id
-            pumpEvent.timestamp = ISO8601DateFormatter().date(from: tempBasalDTO.timestamp)
-            pumpEvent.type = tempBasalDTO._type
-
-            let tempBasal = TempBasalStored(context: context)
-            tempBasal.tempType = tempBasalDTO.temp
-            tempBasal.rate = NSDecimalNumber(value: tempBasalDTO.rate)
-            pumpEvent.tempBasal = tempBasal
-
-            return pumpEvent
-
-        case let .tempBasalDuration(tempBasalDurationDTO):
-            pumpEvent.id = tempBasalDurationDTO.id
-            pumpEvent.timestamp = ISO8601DateFormatter().date(from: tempBasalDurationDTO.timestamp)
-            pumpEvent.type = tempBasalDurationDTO._type
-
-            let tempBasal = TempBasalStored(context: context)
-            tempBasal.duration = Int16(tempBasalDurationDTO.duration)
-            pumpEvent.tempBasal = tempBasal
-
-            return pumpEvent
-
-        case let .suspend(SuspendDTO):
-            pumpEvent.id = SuspendDTO.id
-            pumpEvent.timestamp = ISO8601DateFormatter().date(from: SuspendDTO.timestamp)
-            pumpEvent.type = SuspendDTO._type
-
-            return pumpEvent
-
-        case let .resume(ResumeDTO):
-            pumpEvent.id = ResumeDTO.id
-            pumpEvent.timestamp = ISO8601DateFormatter().date(from: ResumeDTO.timestamp)
-            pumpEvent.type = ResumeDTO._type
-
-            return pumpEvent
-
-        case let .rewind(RewindDTO):
-            pumpEvent.id = RewindDTO.id
-            pumpEvent.timestamp = ISO8601DateFormatter().date(from: RewindDTO.timestamp)
-            pumpEvent.type = RewindDTO._type
-
-            return pumpEvent
-
-        case let .prime(PrimeDTO):
-            pumpEvent.id = PrimeDTO.id
-            pumpEvent.timestamp = ISO8601DateFormatter().date(from: PrimeDTO.timestamp)
-            pumpEvent.type = PrimeDTO._type
-
-            return pumpEvent
-
-        case .unknown:
-            debugPrint("⚠️ Skipping unknown event type.")
-            // Return an empty PumpEventStored object or handle appropriately
-            return PumpEventStored(context: context)
-        }
-    }
-}
-
 // Declare helper structs ("data transfer objects" = DTO) to utilize parsing a flattened pump history
 struct BolusDTO: Codable {
     var id: String
     var timestamp: String
     var amount: Double
     var isExternal: Bool
-    var isSMB: Bool = false
+    var isSMB: Bool
     var duration: Int
     var _type: String = EventType.bolus.rawValue
-
-    private enum CodingKeys: String, CodingKey {
-        case id
-        case timestamp
-        case amount
-        case isExternal = "isExternalInsulin"
-        case isSMB
-        case duration
-        case _type
-    }
 }
 
 struct TempBasalDTO: Codable {
@@ -348,6 +185,36 @@ struct PrimeDTO: Codable {
     var _type: String = EventType.prime.rawValue
 }
 
+// Mask distinct DTO subtypes with a common enum that conforms to Encodable
+enum PumpEventDTO: Encodable {
+    case bolus(BolusDTO)
+    case tempBasal(TempBasalDTO)
+    case tempBasalDuration(TempBasalDurationDTO)
+    case suspend(SuspendDTO)
+    case resume(ResumeDTO)
+    case rewind(RewindDTO)
+    case prime(PrimeDTO)
+
+    func encode(to encoder: Encoder) throws {
+        switch self {
+        case let .bolus(bolus):
+            try bolus.encode(to: encoder)
+        case let .tempBasal(tempBasal):
+            try tempBasal.encode(to: encoder)
+        case let .tempBasalDuration(tempBasalDuration):
+            try tempBasalDuration.encode(to: encoder)
+        case let .suspend(suspend):
+            try suspend.encode(to: encoder)
+        case let .resume(resume):
+            try resume.encode(to: encoder)
+        case let .rewind(rewind):
+            try rewind.encode(to: encoder)
+        case let .prime(prime):
+            try prime.encode(to: encoder)
+        }
+    }
+}
+
 // Extension with helper functions to map pump events to DTO objects via uniform masking enum
 extension PumpEventStored {
     static let dateFormatter: ISO8601DateFormatter = {
@@ -399,7 +266,7 @@ extension PumpEventStored {
         return .tempBasalDuration(tempBasalDurationDTO)
     }
 
-    func toSuspendDTO() -> PumpEventDTO? {
+    func toPumpSuspendDTO() -> PumpEventDTO? {
         guard let id = id, let timestamp = timestamp, let type = type, type == EventType.pumpSuspend.rawValue else {
             return nil
         }
@@ -411,7 +278,7 @@ extension PumpEventStored {
         return .suspend(suspendDTO)
     }
 
-    func toResumeDTO() -> PumpEventDTO? {
+    func toPumpResumeDTO() -> PumpEventDTO? {
         guard let id = id, let timestamp = timestamp, let type = type, type == EventType.pumpResume.rawValue else {
             return nil
         }

+ 8 - 12
Trio.xcodeproj/project.pbxproj

@@ -245,6 +245,7 @@
 		3B4BA78F2D8DC0EC0069D5B8 /* TidepoolServiceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B4BA7882D8DC0EC0069D5B8 /* TidepoolServiceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		3B4BA7902D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B4BA7892D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework */; };
 		3B4BA7912D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B4BA7892D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		3B997DCB2DC00849006B6BB2 /* JSONImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B997DCA2DC00849006B6BB2 /* JSONImporter.swift */; };
 		3BAD36B22D7CDC1A00CC298D /* MainLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */; };
 		3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */; };
 		3BD9687C2D8DDD4600899469 /* SlideButton in Frameworks */ = {isa = PBXBuildFile; productRef = 3BD9687B2D8DDD4600899469 /* SlideButton */; };
@@ -380,7 +381,6 @@
 		BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */; };
 		BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */; };
 		BD793CB22CE8033500D669AC /* TempTargetRunStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */; };
-		BD793CC52CE8ABAD00D669AC /* JSONImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CC42CE8ABA700D669AC /* JSONImporter.swift */; };
 		BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */; };
 		BD7DA9A72AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */; };
 		BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */; };
@@ -539,7 +539,6 @@
 		DD1745502C55CA5500211FAC /* UnitsLimitsSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17454F2C55CA5500211FAC /* UnitsLimitsSettingsProvider.swift */; };
 		DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745512C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift */; };
 		DD1745552C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745542C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift */; };
-		DD1D0BC72DB6BCDE005BD5A1 /* JSONImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1D0BC62DB6BCDE005BD5A1 /* JSONImporterTests.swift */; };
 		DD1DB7CC2BECCA1F0048B367 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */; };
 		DD1E53592D273F26008F32A4 /* LoopStatusHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */; };
 		DD21FCB52C6952AD00AF2C25 /* DecimalPickerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */; };
@@ -662,11 +661,11 @@
 		DDF847E62C5D66490049BB3B /* AddMealPresetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E52C5D66490049BB3B /* AddMealPresetView.swift */; };
 		DDF847E82C5DABA30049BB3B /* WatchConfigAppleWatchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E72C5DABA30049BB3B /* WatchConfigAppleWatchView.swift */; };
 		DDF847EA2C5DABAC0049BB3B /* WatchConfigGarminView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E92C5DABAC0049BB3B /* WatchConfigGarminView.swift */; };
+		DDFF202F2DB1D14500AB8A96 /* NotificationPermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */; };
+		DDFF20312DB1D15500AB8A96 /* BluetoothPermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */; };
 		DDFF204A2DB29EF500AB8A96 /* WatchLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF20492DB29EF500AB8A96 /* WatchLogger.swift */; };
 		DDFF204E2DB2C00B00AB8A96 /* WatchStateSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF204D2DB2C00B00AB8A96 /* WatchStateSnapshot.swift */; };
 		DDFF20502DB2C11900AB8A96 /* WatchStateSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF204F2DB2C11900AB8A96 /* WatchStateSnapshot.swift */; };
-		DDFF202F2DB1D14500AB8A96 /* NotificationPermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */; };
-		DDFF20312DB1D15500AB8A96 /* BluetoothPermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */; };
 		E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFD27368630002FF094 /* ServiceAssembly.swift */; };
 		E00EEC0427368630002FF094 /* SecurityAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFE27368630002FF094 /* SecurityAssembly.swift */; };
 		E00EEC0527368630002FF094 /* StorageAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFF27368630002FF094 /* StorageAssembly.swift */; };
@@ -1043,6 +1042,7 @@
 		3B4BA7692D8DBD690069D5B8 /* RileyLinkKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3B4BA7882D8DC0EC0069D5B8 /* TidepoolServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3B4BA7892D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolServiceKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		3B997DCA2DC00849006B6BB2 /* JSONImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONImporter.swift; sourceTree = "<group>"; };
 		3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoadingView.swift; sourceTree = "<group>"; };
 		3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataInitializationCoordinator.swift; sourceTree = "<group>"; };
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
@@ -1177,7 +1177,6 @@
 		BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetConfiguration.swift; sourceTree = "<group>"; };
 		BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+helper.swift"; sourceTree = "<group>"; };
 		BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetRunStored.swift; sourceTree = "<group>"; };
-		BD793CC42CE8ABA700D669AC /* JSONImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONImporter.swift; sourceTree = "<group>"; };
 		BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigDataFlow.swift; sourceTree = "<group>"; };
 		BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigProvider.swift; sourceTree = "<group>"; };
 		BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorStateModel.swift; sourceTree = "<group>"; };
@@ -1343,7 +1342,6 @@
 		DD17454F2C55CA5500211FAC /* UnitsLimitsSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsProvider.swift; sourceTree = "<group>"; };
 		DD1745512C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsStateModel.swift; sourceTree = "<group>"; };
 		DD1745542C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsRootView.swift; sourceTree = "<group>"; };
-		DD1D0BC62DB6BCDE005BD5A1 /* JSONImporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONImporterTests.swift; sourceTree = "<group>"; };
 		DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = "<group>"; };
 		DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusHelpView.swift; sourceTree = "<group>"; };
 		DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalPickerSettings.swift; sourceTree = "<group>"; };
@@ -1466,11 +1464,11 @@
 		DDF847E52C5D66490049BB3B /* AddMealPresetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddMealPresetView.swift; sourceTree = "<group>"; };
 		DDF847E72C5DABA30049BB3B /* WatchConfigAppleWatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConfigAppleWatchView.swift; sourceTree = "<group>"; };
 		DDF847E92C5DABAC0049BB3B /* WatchConfigGarminView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConfigGarminView.swift; sourceTree = "<group>"; };
+		DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionStepView.swift; sourceTree = "<group>"; };
+		DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothPermissionStepView.swift; sourceTree = "<group>"; };
 		DDFF20492DB29EF500AB8A96 /* WatchLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchLogger.swift; sourceTree = "<group>"; };
 		DDFF204D2DB2C00B00AB8A96 /* WatchStateSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchStateSnapshot.swift; sourceTree = "<group>"; };
 		DDFF204F2DB2C11900AB8A96 /* WatchStateSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchStateSnapshot.swift; sourceTree = "<group>"; };
-		DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionStepView.swift; sourceTree = "<group>"; };
-		DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothPermissionStepView.swift; sourceTree = "<group>"; };
 		E00EEBFD27368630002FF094 /* ServiceAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceAssembly.swift; sourceTree = "<group>"; };
 		E00EEBFE27368630002FF094 /* SecurityAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurityAssembly.swift; sourceTree = "<group>"; };
 		E00EEBFF27368630002FF094 /* StorageAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageAssembly.swift; sourceTree = "<group>"; };
@@ -2533,7 +2531,6 @@
 		38FCF3EE25E9028E0078B0D1 /* TrioTests */ = {
 			isa = PBXGroup;
 			children = (
-				DD1D0BC62DB6BCDE005BD5A1 /* JSONImporterTests.swift */,
 				BD8FC05C2D6618BE00B95AED /* BolusCalculatorTests */,
 				BD8FC0552D66187700B95AED /* CoreDataTests */,
 				38FCF3F125E9028E0078B0D1 /* Info.plist */,
@@ -2639,10 +2636,10 @@
 		587A54C82BCDCE0F009D38E2 /* Model */ = {
 			isa = PBXGroup;
 			children = (
-				BD793CC42CE8ABA700D669AC /* JSONImporter.swift */,
 				3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */,
 				BDF34F8F2C10CF8C00D51995 /* CoreDataStack.swift */,
 				BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */,
+				3B997DCA2DC00849006B6BB2 /* JSONImporter.swift */,
 				DDD1631D2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld */,
 				DDE179112C9100FA003CDDB7 /* Classes+Properties */,
 				5825D1622BD405AE00F36E9B /* Helper */,
@@ -3999,7 +3996,6 @@
 				BD47FDDD2D8B65B10043966B /* GlucoseTargetStepView.swift in Sources */,
 				19D466A329AA2B80004D5F33 /* MealSettingsDataFlow.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
-				BD793CC52CE8ABAD00D669AC /* JSONImporter.swift in Sources */,
 				38AEE73D25F0200C0013F05B /* TrioSettings.swift in Sources */,
 				38FCF3FD25E997A80078B0D1 /* PumpHistoryStorage.swift in Sources */,
 				58645BA72CA2D390008AFCE7 /* ChartAxisSetup.swift in Sources */,
@@ -4401,6 +4397,7 @@
 				DD17453A2C55BFA600211FAC /* AlgorithmAdvancedSettingsDataFlow.swift in Sources */,
 				5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */,
 				E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */,
+				3B997DCB2DC00849006B6BB2 /* JSONImporter.swift in Sources */,
 				BD249D992D42FCCD00412DEB /* BolusStatsSetup.swift in Sources */,
 				9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */,
 				1967DFBE29D052C200759F30 /* Icons.swift in Sources */,
@@ -4564,7 +4561,6 @@
 				CEE9A65E2BBC9F6500EB5194 /* CalibrationsTests.swift in Sources */,
 				BD8FC0622D6619E600B95AED /* OverrideStorageTests.swift in Sources */,
 				BD8FC0592D66189700B95AED /* TestAssembly.swift in Sources */,
-				DD1D0BC72DB6BCDE005BD5A1 /* JSONImporterTests.swift in Sources */,
 				BD8FC0662D661A0000B95AED /* GlucoseStorageTests.swift in Sources */,
 				BD8FC05B2D6618AF00B95AED /* DeterminationStorageTests.swift in Sources */,
 				CE1F6DD92BADF4620064EB8D /* PluginManagerTests.swift in Sources */,

+ 2 - 2
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -227,10 +227,10 @@ final class OpenAPS {
             if let tempBasalDTO = event.toTempBasalDTOEnum() {
                 eventDTOs.append(tempBasalDTO)
             }
-            if let pumpSuspendDTO = event.toSuspendDTO() {
+            if let pumpSuspendDTO = event.toPumpSuspendDTO() {
                 eventDTOs.append(pumpSuspendDTO)
             }
-            if let pumpResumeDTO = event.toResumeDTO() {
+            if let pumpResumeDTO = event.toPumpResumeDTO() {
                 eventDTOs.append(pumpResumeDTO)
             }
             if let rewindDTO = event.toRewindDTO() {

+ 0 - 53
Trio/Sources/Helpers/CheckboxToggleStyle.swift

@@ -1,53 +0,0 @@
-import SwiftUI
-
-struct RadioButtonToggleStyle: ToggleStyle {
-    func makeBody(configuration: Self.Configuration) -> some View {
-        HStack {
-            Circle()
-                .stroke(lineWidth: 2)
-                .foregroundColor(.secondary)
-                .frame(width: 20, height: 20)
-                .overlay {
-                    if configuration.isOn {
-                        Image(systemName: "circle.fill")
-                    }
-                }
-                .onTapGesture {
-                    withAnimation {
-                        configuration.isOn.toggle()
-                    }
-                }
-            configuration.label
-        }
-    }
-}
-
-struct CheckboxToggleStyle: ToggleStyle {
-    var tint = Color.primary
-
-    func makeBody(configuration: Configuration) -> some View {
-        HStack {
-            RoundedRectangle(cornerRadius: 5)
-                .stroke(lineWidth: 2)
-                .foregroundColor(Color.secondary)
-                .frame(width: 20, height: 20)
-                .overlay {
-                    if configuration.isOn {
-                        Image(systemName: "checkmark")
-                            .font(.body)
-                            .fontWeight(.bold)
-                            .foregroundColor(tint)
-                    }
-                }
-                .onTapGesture {
-                    configuration.isOn.toggle()
-                }
-
-            configuration.label
-        }
-        .contentShape(Rectangle()) // make entire HStack tappable
-        .onTapGesture {
-            configuration.isOn.toggle()
-        }
-    }
-}

+ 6 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -31272,6 +31272,9 @@
         }
       }
     },
+    "After completing onboarding, a red banner will appear on Trio's main screen to guide you to the iOS Settings app, where you can enable notifications." : {
+
+    },
     "Agree and continue" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -154817,6 +154820,9 @@
         }
       }
     },
+    "Notifications for “Trio” are Disabled" : {
+
+    },
     "Notifications give you important Trio information without requiring you to open the app." : {
       "localizations" : {
         "bg" : {

+ 1 - 2
Trio/Sources/Models/Determination.swift

@@ -1,7 +1,6 @@
-import CoreData
 import Foundation
 
-struct Determination: JSON, Equatable, Decodable {
+struct Determination: JSON, Equatable {
     let id: UUID?
     var reason: String
     let units: Decimal?

+ 1 - 17
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -16,7 +16,6 @@ extension Onboarding {
         @ObservationIgnored @Injected() var notificationsManager: UserNotificationsManager!
         @ObservationIgnored @Injected() var bluetoothManager: BluetoothStateManager!
 
-        private let coreDataStack = CoreDataStack.shared
         private let settingsProvider = PickerSettingsProvider.shared
 
         // MARK: - App Diagnostics
@@ -143,6 +142,7 @@ extension Onboarding {
         // MARK: - Permission Requests
 
         var hasNotificationsGranted = false
+        var shouldDisplayCustomNotificationAlert: Bool = false
 
         var shouldDisplayBluetoothRequestAlert: Bool = false
         var hasBluetoothGranted = false
@@ -569,7 +569,6 @@ extension Onboarding {
             saveBasalProfile()
             saveCarbRatios()
             saveISFValues()
-            migrateDataFromJSON()
         }
 
         /// Persists the current diagnostics sharing option to UserDefaults as a boolean.
@@ -629,21 +628,6 @@ extension Onboarding {
             let pumpSettings = PumpSettings(insulinActionCurve: defaultDIA, maxBolus: maxBolus, maxBasal: maxBasal)
             fileStorage.save(pumpSettings, as: OpenAPS.Settings.settings)
         }
-
-        func migrateDataFromJSON() {
-            Task {
-                let importer = JSONImporter(context: coreDataStack.newTaskContext())
-                async let importPumpHistory: () = importer.importPumpHistoryIfNeeded()
-                async let importCarbHistory: () = importer.importCarbHistoryIfNeeded()
-                async let importGlucoseHistory: () = importer.importGlucoseHistoryIfNeeded()
-                async let importDeterminationHistory: () = importer.importDeterminationHistoryIfNeeded()
-
-                await importPumpHistory
-                await importCarbHistory
-                await importGlucoseHistory
-                await importDeterminationHistory
-            }
-        }
     }
 }
 

+ 17 - 4
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -310,7 +310,7 @@ struct OnboardingStepContent: View {
                         case .targetBehavior:
                             AlgorithmSettingsSubstepView(state: state, substep: currentTargetBehaviorSubstep)
                         case .notifications:
-                            NotificationPermissionStepView()
+                            NotificationPermissionStepView(state: state, currentStep: $currentStep)
                         case .bluetooth:
                             BluetoothPermissionStepView(
                                 state: state,
@@ -547,10 +547,23 @@ struct OnboardingNavigationButtons: View {
 
         case .notifications:
             currentTargetBehaviorSubstep = .halfBasalTarget
+
             if let next = currentStep.next {
-                DispatchQueue.main.async {
-                    state.notificationsManager.requestNotificationPermissions { granted in
-                        state.hasNotificationsGranted = granted
+                state.notificationsManager.getNotificationSettings { notificationSettings in
+                    switch notificationSettings.authorizationStatus {
+                    case .notDetermined:
+                        state.notificationsManager.requestNotificationPermissions { granted in
+                            state.hasNotificationsGranted = granted
+                            currentStep = next
+                        }
+                    case .denied:
+                        state.shouldDisplayCustomNotificationAlert = true
+                    case .authorized,
+                         .ephemeral,
+                         .provisional:
+                        currentStep = next
+                        break
+                    @unknown default:
                         currentStep = next
                     }
                 }

+ 0 - 42
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/LogoAnimation.swift

@@ -1,42 +0,0 @@
-//
-//  LogoAnimation.swift
-//  Trio
-//
-//  Created by Marvin Polscheit on 11.04.25.
-//
-import SwiftUI
-
-struct PulsingLogoAnimation: View {
-    @State private var scale = 0.5
-    @State private var opacity = 0.0
-    @State private var rotation = 0.0
-    @State private var isPulsing = false
-
-    var body: some View {
-        Image("trioCircledNoBackground")
-            .resizable()
-            .scaledToFit()
-            .frame(height: 100)
-            .scaleEffect(scale)
-            .opacity(opacity)
-            .rotationEffect(.degrees(rotation))
-            .scaleEffect(isPulsing ? 1.1 : 1.0)
-            .onAppear {
-                withAnimation(.easeInOut(duration: 1.0)) {
-                    scale = 1.0
-                    opacity = 1.0
-                    rotation = 360
-                }
-
-                withAnimation(.easeInOut(duration: 1.0).repeatForever()) {
-                    isPulsing.toggle()
-                }
-
-                DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
-                    withAnimation(.easeOut(duration: 1.0)) {
-                        isPulsing = false
-                    }
-                }
-            }
-    }
-}

+ 30 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/NotificationPermissionStepView.swift

@@ -8,6 +8,9 @@ import SwiftUI
 import UserNotifications
 
 struct NotificationPermissionStepView: View {
+    @Bindable var state: Onboarding.StateModel
+    var currentStep: Binding<OnboardingStep>
+
     var body: some View {
         VStack(alignment: .leading, spacing: 20) {
             Text("Allow Notifications")
@@ -64,5 +67,32 @@ struct NotificationPermissionStepView: View {
                 .foregroundColor(Color.secondary)
                 .padding(.top)
         }.padding(.horizontal)
+            .background(
+                SystemAlert(
+                    isPresented: $state.shouldDisplayCustomNotificationAlert,
+                    title: String(localized: "Notifications for “Trio” are Disabled"),
+                    message: String(
+                        localized: "After completing onboarding, a red banner will appear on Trio's main screen to guide you to the iOS Settings app, where you can enable notifications."
+                    ),
+                    allowTitle: String(localized: "Got it!"),
+                    denyTitle: String(localized: "Cancel"),
+                    onAllow: {
+                        DispatchQueue.main.async {
+                            state.shouldDisplayCustomNotificationAlert = false
+                            if let next = currentStep.wrappedValue.next {
+                                currentStep.wrappedValue = next
+                            }
+                        }
+                    },
+                    onDeny: {
+                        DispatchQueue.main.async {
+                            state.shouldDisplayCustomNotificationAlert = false
+                            if let next = currentStep.wrappedValue.next {
+                                currentStep.wrappedValue = next
+                            }
+                        }
+                    }
+                )
+            )
     }
 }

+ 18 - 18
Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift

@@ -9,6 +9,7 @@ import UIKit
 import UserNotifications
 
 protocol UserNotificationsManager {
+    func getNotificationSettings(completionHandler: @escaping (UNNotificationSettings) -> Void)
     func requestNotificationPermissions(completion: @escaping (Bool) -> Void)
 }
 
@@ -61,7 +62,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
 
     @Persisted(key: "UserNotificationsManager.snoozeUntilDate") private var snoozeUntilDate: Date = .distantPast
 
-    private let center = UNUserNotificationCenter.current()
+    private let notificationCenter = UNUserNotificationCenter.current()
     private var lifetime = Lifetime()
 
     private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
@@ -77,7 +78,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
 
     init(resolver: Resolver) {
         super.init()
-        center.delegate = self
+        notificationCenter.delegate = self
         injectServices(resolver)
 
         coreDataPublisher =
@@ -132,7 +133,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     private func addAppBadge(glucose: Int?) {
         guard let glucose = glucose, settingsManager.settings.glucoseBadge else {
             DispatchQueue.main.async {
-                self.center.setBadgeCount(0) { error in
+                self.notificationCenter.setBadgeCount(0) { error in
                     guard let error else {
                         return
                     }
@@ -150,7 +151,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
 
         DispatchQueue.main.async {
-            self.center.setBadgeCount(badge) { error in
+            self.notificationCenter.setBadgeCount(badge) { error in
                 guard let error else {
                     return
                 }
@@ -386,18 +387,17 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         return body
     }
 
-//    private func requestNotificationPermissionsIfNeeded() {
-//        center.getNotificationSettings { settings in
-//            debug(.service, "UNUserNotificationCenter.authorizationStatus: \(String(describing: settings.authorizationStatus))")
-//            if ![.authorized, .provisional].contains(settings.authorizationStatus) {
-//                self.requestNotificationPermissions()
-//            }
-//        }
-//    }
+    func getNotificationSettings(completionHandler: @escaping (UNNotificationSettings) -> Void) {
+        notificationCenter.getNotificationSettings { settings in
+            DispatchQueue.main.async {
+                completionHandler(settings)
+            }
+        }
+    }
 
     func requestNotificationPermissions(completion: @escaping (Bool) -> Void) {
         debug(.service, "requestNotificationPermissions")
-        center.requestAuthorization(options: [.badge, .sound, .alert]) { granted, error in
+        notificationCenter.requestAuthorization(options: [.badge, .sound, .alert]) { granted, error in
             if granted {
                 debug(.service, "requestNotificationPermissions was granted")
                 DispatchQueue.main.async {
@@ -432,8 +432,8 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
             .title : (identifier == .alertMessageNotification ? alertIdentifier + content.body : alertIdentifier)
         if deleteOld {
             DispatchQueue.main.async {
-                self.center.removeDeliveredNotifications(withIdentifiers: [alertIdentifier])
-                self.center.removePendingNotificationRequests(withIdentifiers: [alertIdentifier])
+                self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [alertIdentifier])
+                self.notificationCenter.removePendingNotificationRequests(withIdentifiers: [alertIdentifier])
             }
         }
         if alertPermissionsChecker.notificationsDisabled {
@@ -444,7 +444,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
 
         let request = UNNotificationRequest(identifier: alertIdentifier, content: content, trigger: trigger)
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-            self.center.add(request) { error in
+            self.notificationCenter.add(request) { error in
                 if let error = error {
                     warning(.service, "Unable to addNotificationRequest", error: error)
                     return
@@ -563,8 +563,8 @@ extension BaseUserNotificationsManager: pumpNotificationObserver {
     func pumpRemoveNotification() {
         let identifier: Identifier = .pumpNotification
         DispatchQueue.main.async {
-            self.center.removeDeliveredNotifications(withIdentifiers: [identifier.rawValue])
-            self.center.removePendingNotificationRequests(withIdentifiers: [identifier.rawValue])
+            self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier.rawValue])
+            self.notificationCenter.removePendingNotificationRequests(withIdentifiers: [identifier.rawValue])
         }
     }
 }

+ 2 - 3
TrioTests/BolusCalculatorTests/BolusCalculatorTests.swift

@@ -332,15 +332,14 @@ import Testing
 
     @Test("Calculate insulin with zero carbs") func testZeroCarbsCalculation() async throws {
         // Given
-        let carbs: Decimal = 0.0
-        let minPredBG: Decimal = 80.0
+        let carbs: Decimal = 0
 
         // When
         let result = await calculator.handleBolusCalculation(
             carbs: carbs,
             useFattyMealCorrection: false,
             useSuperBolus: false,
-            minPredBG: minPredBG
+            minPredBG: nil
         )
 
         // Then

+ 0 - 488
TrioTests/JSONImporterTests.swift

@@ -1,488 +0,0 @@
-//
-//  JSONImporterTests.swift
-//  Trio
-//
-//  Created by Cengiz Deniz on 21.04.25.
-//
-import CoreData
-import Foundation
-import Swinject
-import Testing
-
-@testable import Trio
-
-@Suite("JSON Importer Tests") struct JSONImporterTests: Injectable {
-    let resolver: Resolver = TrioApp().resolver
-    var coreDataStack: CoreDataStack!
-    var context: NSManagedObjectContext!
-    var importer: JSONImporter!
-    let fileManager = FileManager.default
-    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-    @Injected() var fileStorage: FileStorage!
-
-    init() async throws {
-        injectServices(resolver)
-
-        // In-memory Core Data for tests
-        coreDataStack = try await CoreDataStack.createForTests()
-        context = coreDataStack.newTaskContext()
-        importer = JSONImporter(context: context)
-
-        // Clear import flags and remove fixtures
-        let flags = [
-            "pumpHistoryImported",
-            "carbHistoryImported",
-            "glucoseHistoryImported",
-            "enactedHistoryImported"
-        ]
-        flags.forEach { UserDefaults.standard.removeObject(forKey: $0) }
-        let comps = [
-            OpenAPS.Monitor.pumpHistory,
-            OpenAPS.Monitor.carbHistory,
-            OpenAPS.Monitor.glucose,
-            OpenAPS.Enact.enacted
-        ]
-        comps.forEach { try? fileManager.removeItem(at: documentsURL.appendingPathComponent($0)) }
-    }
-
-    private let iso8601WithFractionalSecondsFormatter: ISO8601DateFormatter = {
-        let formatter = ISO8601DateFormatter()
-        // ensure it parses the full internet date+time with milliseconds
-        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
-        return formatter
-    }()
-
-    /// Parses an ISO‑8601 string (e.g. "2025-04-17T10:00:00.000Z") into a `Date`.
-    /// - Parameter isoString: the ISO‑8601 date string.
-    /// - Returns: a `Date` if parsing succeeds, or `nil` otherwise.
-    func dateFromISOString(_ isoString: String) -> Date? {
-        iso8601WithFractionalSecondsFormatter.date(from: isoString)
-    }
-
-    @Test("Import pump history with value checks") func testImportPumpHistoryDetails() async throws {
-        let pumpHistory = [
-            PumpHistoryEvent(
-                id: "9DDAA42F-465C-4812-9422-9933FB1CC290",
-                type: .bolus,
-                timestamp: dateFromISOString("2025-04-17T10:00:00.000Z") ?? Date(),
-                amount: 1.0,
-                duration: 0,
-                isSMB: false,
-                isExternal: true
-            ),
-            PumpHistoryEvent(
-                id: "F958F9A5-78F3-4B6C-AF6C-5B580BBB8A29",
-                type: .bolus,
-                timestamp: dateFromISOString("2025-04-17T10:01:00.000Z") ?? Date(),
-                amount: 2.0,
-                duration: 0,
-                isSMB: false,
-                isExternal: false
-            ),
-            PumpHistoryEvent(
-                id: "CCBE1CDA-EE13-4D7C-8CCC-7361EC9C979D",
-                type: .bolus,
-                timestamp: dateFromISOString("2025-04-17T10:02:00.000Z") ?? Date(),
-                amount: 3.0,
-                duration: 0,
-                isSMB: true,
-                isExternal: false
-            ),
-            PumpHistoryEvent(
-                id: "0FB76585-B6A4-4659-BDD2-B673BE6DD549",
-                type: .tempBasalDuration,
-                timestamp: dateFromISOString("2025-04-17T10:05:00.000Z") ?? Date(),
-                duration: 30
-            ),
-            PumpHistoryEvent(
-                id: "_0FB76585-B6A4-4659-BDD2-B673BE6DD549",
-                type: .tempBasal,
-                timestamp: dateFromISOString("2025-04-17T10:05:00.000Z") ?? Date(),
-                amount: 1.5,
-                duration: 0,
-                temp: .absolute
-            ),
-            PumpHistoryEvent(
-                id: "24909A93-0BC7-46D0-837F-9B2028E22BFC",
-                type: .pumpSuspend,
-                timestamp: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date()
-            ),
-            PumpHistoryEvent(
-                id: "BDEF7F55-48FE-447D-876C-19260ADE5ECA",
-                type: .pumpResume,
-                timestamp: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date()
-            ),
-            PumpHistoryEvent(
-                id: "1CAEEFA3-D740-4EA0-83B4-D28860991639",
-                type: .rewind,
-                timestamp: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date()
-            ),
-            PumpHistoryEvent(
-                id: "CD019C44-57F0-4CB0-BBDF-8B6C40A48E99",
-                type: .prime,
-                timestamp: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date()
-            )
-        ]
-
-        fileStorage.save(pumpHistory, as: OpenAPS.Monitor.pumpHistory)
-
-        // Import
-        await importer.importPumpHistoryIfNeeded()
-
-        // Fetch all imported events
-        let events = try await coreDataStack.fetchEntitiesAsync(
-            ofType: PumpEventStored.self,
-            onContext: context,
-            predicate: NSPredicate(format: "TRUEPREDICATE"),
-            key: "timestamp",
-            ascending: true
-        ) as? [PumpEventStored] ?? []
-
-        // Verify total count
-        #expect(events.count == 8, "Should import all 8 pump events") // TBR should be combination of TB duration and TBR, so 8, not 9
-
-        let iso8601 = ISO8601DateFormatter()
-        iso8601.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
-
-        // Verify the three Boluses
-        let bolusEvents = events.filter { $0.type == PumpEventStored.EventType.bolus.rawValue }
-        #expect(bolusEvents.count == 3, "Three bolus events")
-        let extDate = iso8601.date(from: "2025-04-17T10:00:00.000Z")!
-        let nonExtDate = iso8601.date(from: "2025-04-17T10:01:00.000Z")!
-        let smbDate = iso8601.date(from: "2025-04-17T10:02:00.000Z")!
-
-        // external bolus
-        #expect(
-            bolusEvents.contains {
-                abs($0.timestamp!.timeIntervalSince(extDate)) < 0.001 &&
-                    $0.bolus?.amount == NSDecimalNumber(value: 1.0) &&
-                    $0.bolus?.isExternal == true &&
-                    $0.bolus?.isSMB == false
-            },
-            "External bolus (1.0 U) at 10:00"
-        )
-        // non‑external
-        #expect(
-            bolusEvents.contains {
-                abs($0.timestamp!.timeIntervalSince(nonExtDate)) < 0.001 &&
-                    $0.bolus?.amount == NSDecimalNumber(value: 2.0) &&
-                    $0.bolus?.isExternal == false &&
-                    $0.bolus?.isSMB == false
-            },
-            "Non‑external bolus (2.0 U) at 10:01"
-        )
-        // SMB
-        #expect(
-            bolusEvents.contains {
-                abs($0.timestamp!.timeIntervalSince(smbDate)) < 0.001 &&
-                    $0.bolus?.amount == NSDecimalNumber(value: 3.0) &&
-                    $0.bolus?.isExternal == false &&
-                    $0.bolus?.isSMB == true
-            },
-            "SMB bolus (3.0 U) at 10:02"
-        )
-
-        // Verify TempBasalDuration + TempBasal
-        let durDate = iso8601.date(from: "2025-04-17T10:05:00.000Z")!
-        let durEvt = events.first {
-            abs($0.timestamp!.timeIntervalSince(durDate)) < 0.001 &&
-                $0.tempBasal?.duration == 30 &&
-                $0.tempBasal?.rate == NSDecimalNumber(value: 1.5)
-        }
-        #expect(durEvt != nil, "TempBasalRate at 10:05 for 30 min with rate 1.5 U/h")
-
-        // Verify the four “marker” events
-        let markers: [(type: PumpEventStored.EventType, ts: String)] = [
-            (.pumpSuspend, "2025-04-17T10:10:00.000Z"),
-            (.pumpResume, "2025-04-17T10:15:00.000Z"),
-            (.rewind, "2025-04-17T10:20:00.000Z"),
-            (.prime, "2025-04-17T10:25:00.000Z")
-        ]
-
-        for (eventType, tsString) in markers {
-            let date = iso8601.date(from: tsString)!
-            #expect(
-                events.contains {
-                    $0.type == eventType.rawValue &&
-                        abs($0.timestamp!.timeIntervalSince(date)) < 0.001
-                },
-                "\(eventType) at \(tsString)"
-            )
-        }
-
-        // Ensure file cleaned up and flag set
-        let url = documentsURL.appendingPathComponent(OpenAPS.Monitor.pumpHistory)
-        #expect(!fileManager.fileExists(atPath: url.path), "Pump JSON should be removed")
-        #expect(UserDefaults.standard.bool(forKey: "pumpHistoryImported"))
-    }
-
-
-    @Test("Import carb history with property checks") func testImportCarbHistoryDetails() async throws {
-        let carbHistory = [
-            CarbsEntry(
-                id: "CF9BE626-5B4F-421C-825F-BDEB873FF385",
-                createdAt: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date(),
-                actualDate: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date(),
-                carbs: 2,
-                fat: 0,
-                protein: 0,
-                note: "",
-                enteredBy: "Trio",
-                isFPU: true,
-                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
-            ),
-            CarbsEntry(
-                id: "6FFED023-DE5C-4042-8E4D-D876C37F528C",
-                createdAt: dateFromISOString("2025-04-21T21:58:25.452Z") ?? Date(),
-                actualDate: dateFromISOString("2025-04-21T21:58:25.452Z") ?? Date(),
-                carbs: 2,
-                fat: 0,
-                protein: 0,
-                note: "",
-                enteredBy: "Trio",
-                isFPU: true,
-                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
-            ),
-            CarbsEntry(
-                id: "32859426-03FC-4CF7-B9A1-16E122C04889",
-                createdAt: dateFromISOString("2025-04-21T21:28:25.452Z") ?? Date(),
-                actualDate: dateFromISOString("2025-04-21T21:28:25.452Z") ?? Date(),
-                carbs: 2,
-                fat: 0,
-                protein: 0,
-                note: "",
-                enteredBy: "Trio",
-                isFPU: true,
-                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
-            ),
-            CarbsEntry(
-                id: "F9AA11B6-8B2E-4FCA-9E3C-EFE582786CFD",
-                createdAt: dateFromISOString("2025-04-21T20:58:25.452Z") ?? Date(),
-                actualDate: dateFromISOString("2025-04-21T20:58:25.452Z") ?? Date(),
-                carbs: 2,
-                fat: 0,
-                protein: 0,
-                note: "",
-                enteredBy: "Trio",
-                isFPU: true,
-                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
-            ),
-            CarbsEntry(
-                id: "D2986C75-8EEF-4ACB-AE62-35B5391D437D",
-                createdAt: dateFromISOString("2025-04-21T20:28:25.452Z") ?? Date(),
-                actualDate: dateFromISOString("2025-04-21T20:28:25.452Z") ?? Date(),
-                carbs: 2,
-                fat: 0,
-                protein: 0,
-                note: "",
-                enteredBy: "Trio",
-                isFPU: true,
-                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
-            ),
-            CarbsEntry(
-                id: "E2F186B2-6A8F-4BC4-A038-3C104F988A78",
-                createdAt: dateFromISOString("2025-04-21T19:58:25.452Z") ?? Date(),
-                actualDate: dateFromISOString("2025-04-21T19:58:25.452Z") ?? Date(),
-                carbs: 2,
-                fat: 0,
-                protein: 0,
-                note: "",
-                enteredBy: "Trio",
-                isFPU: true,
-                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
-            ),
-            CarbsEntry(
-                id: "EEE7F9A0-490C-4E2B-8F04-ED8A77FC7867",
-                createdAt: dateFromISOString("2025-04-21T18:58:25.452Z") ?? Date(),
-                actualDate: dateFromISOString("2025-04-21T18:58:25.452Z") ?? Date(),
-                carbs: 45,
-                fat: 15,
-                protein: 25,
-                note: "",
-                enteredBy: "Trio",
-                isFPU: true,
-                fpuID: nil
-            ),
-            CarbsEntry(
-                id: "283155A7-5AF0-486E-BD3B-F9F8E2354845",
-                createdAt: dateFromISOString("2025-04-21T16:50:02.104Z") ?? Date(),
-                actualDate: dateFromISOString("2025-04-21T16:50:02.104Z") ?? Date(),
-                carbs: 30,
-                fat: 0,
-                protein: 0,
-                note: "",
-                enteredBy: "Trio",
-                isFPU: true,
-                fpuID: nil
-            )
-        ]
-        
-        fileStorage.save(carbHistory, as: OpenAPS.Monitor.carbHistory)
-
-        await importer.importCarbHistoryIfNeeded()
-
-        // Fetch all imported events
-        let entries = try await coreDataStack.fetchEntitiesAsync(
-            ofType: CarbEntryStored.self,
-            onContext: context,
-            predicate: NSPredicate(format: "TRUEPREDICATE"),
-            key: "date",
-            ascending: false
-        ) as? [CarbEntryStored] ?? []
-
-        #expect(entries.count == 8, "Should import 8 carb entries")
-        
-        // TODO: add distinct tests
-
-        let url = documentsURL.appendingPathComponent(OpenAPS.Monitor.carbHistory)
-        #expect(!fileManager.fileExists(atPath: url.path))
-        #expect(UserDefaults.standard.bool(forKey: "carbHistoryImported"))
-    }
-
-    @Test("Import glucose history with manual flag checks") func testImportGlucoseHistoryDetails() async throws {
-        let glucoseReadings = [
-            BloodGlucose(
-                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
-                sgv: 107,
-                direction: .flat,
-                date: 1733677520950,
-                dateString: dateFromISOString("2024-08-23T20:24:07.950Z") ?? Date(),
-                unfiltered: 107,
-                filtered: nil,
-                noise: nil,
-                glucose: 107,
-                type: "sgv",
-                transmitterID: "ABC123"
-            ),
-            BloodGlucose(
-                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
-                sgv: 112,
-                direction: .fortyFiveUp,
-                date: 1733676920294,
-                dateString: dateFromISOString("2024-12-08T16:55:20.295Z") ?? Date(),
-                unfiltered: 112,
-                filtered: nil,
-                noise: nil,
-                glucose: 112,
-                type: "sgv",
-                transmitterID: "ABC123"
-            ),
-            BloodGlucose(
-                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
-                sgv: 97,
-                direction: .fortyFiveDown,
-                date: 1733676620784,
-                dateString: dateFromISOString("2024-12-08T16:50:20.784Z") ?? Date(),
-                unfiltered: 97,
-                filtered: nil,
-                noise: nil,
-                glucose: 97,
-                type: "sgv",
-                transmitterID: "ABC123"
-            ),
-            BloodGlucose(
-                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
-                sgv: 70,
-                direction: .doubleDown,
-                date: 1733676320525,
-                dateString: dateFromISOString("2024-12-08T16:45:20.525Z") ?? Date(),
-                unfiltered: 70,
-                filtered: nil,
-                noise: nil,
-                glucose: 70,
-                type: "sgv",
-                transmitterID: "ABC123"
-            ),
-            BloodGlucose(
-                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
-                sgv: 188,
-                direction: .doubleUp,
-                date: 1733676020918,
-                dateString: dateFromISOString("2024-12-08T16:40:20.919Z") ?? Date(),
-                unfiltered: 188,
-                filtered: nil,
-                noise: nil,
-                glucose: 188,
-                type: "sgv",
-                transmitterID: "ABC123"
-            )
-        ]
-
-        // Fetch all GlucoseStored entries sorted by date
-        let allReadings = try await coreDataStack.fetchEntitiesAsync(
-            ofType: GlucoseStored.self,
-            onContext: context,
-            predicate: NSPredicate(format: "TRUEPREDICATE"),
-            key: "date",
-            ascending: true
-        ) as? [GlucoseStored] ?? []
-
-        #expect(allReadings.count == 5, "Should have imported 5 glucose readings")
-
-        // TODO: add distinct tests
-        
-        let url = documentsURL.appendingPathComponent(OpenAPS.Monitor.glucose)
-        #expect(!fileManager.fileExists(atPath: url.path))
-        #expect(UserDefaults.standard.bool(forKey: "glucoseHistoryImported"))
-    }
-
-    @Test("Import determination history with nested predBGs and values") func testImportDeterminationHistoryDetails() async throws {
-        let iobValues: [Int] = [
-            153, 149, 144, 139, 134, 129, 124, 119, 114, 109,
-            103,  98,  92,  87,  81,  76,  71,  65,  60,  55,
-             50,  44,  39
-        ]
-
-        let ztValues: [Int] = [
-            153, 147, 140, 134, 128, 121, 115, 109, 104,  98,
-             93,  87,  83,  78,  74,  70,  66,  63,  60,  57,
-             55,  53,  52,  51,  51,  51,  51,  52,  53,  54,
-             56,  58,  60,  62,  65,  67,  70,  73,  76,  78,
-             81,  84
-        ]
-
-        let uamValues: [Int] = [
-            153, 147, 140, 134, 127, 121, 115, 108, 102,  96,
-             89,  83,  77,  71,  65,  58,  52,  45,  39
-        ]
-
-        let determination = Determination(
-            id: UUID(),
-            reason: "Autosens ratio: 0.94, ISF: 45→48, COB: 0, Dev: 13, BGI: -6, CR: 7.8→8.3, Target: 85, minPredBG 45, minGuardBG -53, IOBpredBG 39, UAMpredBG 39, TDD: 42.2 U, 89% Bolus 11% Basal, Dynamic ISF/CR: On/On, Logarithmic formula, AF: 0.8, Basal ratio: 1.01; minGuardBG -53<70",
-            units: nil,
-            insulinReq: Decimal(0),
-            eventualBG: 46,
-            sensitivityRatio: Decimal(0.9430005356061704),
-            rate: Decimal(0),
-            duration: Decimal(120),
-            iob: Decimal(2.52),
-            cob: Decimal(0),
-            predictions: Predictions(iob: iobValues, zt: ztValues, cob: nil, uam: uamValues),
-            deliverAt:    dateFromISOString("2024-08-01T09:42:08.734Z") ?? Date(),
-            carbsReq:     nil,
-            temp:         TempType(rawValue: "absolute"),
-            bg:           Decimal(153),
-            reservoir:    Decimal(3735928559),
-            isf:          Decimal(48),
-            timestamp:    dateFromISOString("2024-08-01T09:42:09.371Z") ?? Date(),
-            current_target: Decimal(85),
-            insulinForManualBolus: nil,
-            manualBolusErrorString: Decimal(2),
-            minDelta:     Decimal(-4.28),
-            expectedDelta: Decimal(-4.7),
-            minGuardBG:   Decimal(-53),
-            minPredBG:    nil,
-            threshold:    Decimal(70),
-            carbRatio:    Decimal(8.3),
-            received:     true
-        )
-        
-        fileStorage.save(determination, as: OpenAPS.Enact.enacted)
-
-        // TODO: add distinct tests
-
-        let url = documentsURL.appendingPathComponent(OpenAPS.Enact.enacted)
-        #expect(!fileManager.fileExists(atPath: url.path))
-        #expect(UserDefaults.standard.bool(forKey: "enactedHistoryImported"))
-    }
-}