Prechádzať zdrojové kódy

Add CoreData entity, minor refactoring to prepare storage WIP

Deniz Cengiz 1 rok pred
rodič
commit
3bac02a3ed

+ 26 - 18
FreeAPS.xcodeproj/project.pbxproj

@@ -465,6 +465,7 @@
 		DD9ECB712CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6E2CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift */; };
 		DD9ECB722CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */; };
 		DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */; };
+		DDB37CC52D05048F00D99BF4 /* ContactTrickStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactTrickStorage.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
 		DDD163122C4C689900CD525A /* OverrideStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163112C4C689900CD525A /* OverrideStateModel.swift */; };
 		DDD163142C4C68D300CD525A /* OverrideProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163132C4C68D300CD525A /* OverrideProvider.swift */; };
@@ -1153,6 +1154,9 @@
 		DD9ECB6E2CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigProvider.swift; sourceTree = "<group>"; };
 		DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigDataFlow.swift; sourceTree = "<group>"; };
 		DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfig.swift; sourceTree = "<group>"; };
+		DDB37CC22D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
+		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
+		DDB37CC42D05048F00D99BF4 /* ContactTrickStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickStorage.swift; sourceTree = "<group>"; };
 		DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivity+Helper.swift"; sourceTree = "<group>"; };
 		DDD163112C4C689900CD525A /* OverrideStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStateModel.swift; sourceTree = "<group>"; };
 		DDD163132C4C68D300CD525A /* OverrideProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideProvider.swift; sourceTree = "<group>"; };
@@ -2063,6 +2067,7 @@
 		38A0362725ECF05300FCBB52 /* Storage */ = {
 			isa = PBXGroup;
 			children = (
+				DDB37CC42D05048F00D99BF4 /* ContactTrickStorage.swift */,
 				385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */,
 				38AEE75625F0F18E0013F05B /* CarbsStorage.swift */,
 				38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */,
@@ -2819,38 +2824,40 @@
 		DDE179112C9100FA003CDDB7 /* Classes+Properties */ = {
 			isa = PBXGroup;
 			children = (
-				DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */,
-				DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */,
-				DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */,
-				DDE179352C910127003CDDB7 /* LoopStatRecord+CoreDataProperties.swift */,
 				DDE179362C910127003CDDB7 /* BolusStored+CoreDataClass.swift */,
 				DDE179372C910127003CDDB7 /* BolusStored+CoreDataProperties.swift */,
-				DDE179382C910127003CDDB7 /* ForecastValue+CoreDataClass.swift */,
-				DDE179392C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift */,
 				DDE1793A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift */,
 				DDE1793B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift */,
-				DDE1793C2C910127003CDDB7 /* TempTargets+CoreDataClass.swift */,
-				DDE1793D2C910127003CDDB7 /* TempTargets+CoreDataProperties.swift */,
-				DDE1793E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift */,
-				DDE1793F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift */,
-				DDE179402C910127003CDDB7 /* StatsData+CoreDataClass.swift */,
-				DDE179412C910127003CDDB7 /* StatsData+CoreDataProperties.swift */,
+				DDB37CC22D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataClass.swift */,
+				DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */,
 				DDE179422C910127003CDDB7 /* Forecast+CoreDataClass.swift */,
 				DDE179432C910127003CDDB7 /* Forecast+CoreDataProperties.swift */,
+				DDE179382C910127003CDDB7 /* ForecastValue+CoreDataClass.swift */,
+				DDE179392C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift */,
 				DDE179442C910127003CDDB7 /* GlucoseStored+CoreDataClass.swift */,
 				DDE179452C910127003CDDB7 /* GlucoseStored+CoreDataProperties.swift */,
+				DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */,
+				DDE179352C910127003CDDB7 /* LoopStatRecord+CoreDataProperties.swift */,
+				DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */,
+				DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */,
 				DDE179462C910127003CDDB7 /* OpenAPS_Battery+CoreDataClass.swift */,
 				DDE179472C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift */,
-				DDE179482C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift */,
-				DDE179492C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift */,
-				DDE1794A2C910127003CDDB7 /* TempTargetsSlider+CoreDataClass.swift */,
-				DDE1794B2C910127003CDDB7 /* TempTargetsSlider+CoreDataProperties.swift */,
-				DDE1794C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift */,
-				DDE1794D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift */,
 				DDE1794E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift */,
 				DDE1794F2C910127003CDDB7 /* OrefDetermination+CoreDataProperties.swift */,
+				DDE1794C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift */,
+				DDE1794D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift */,
 				DDE179502C910127003CDDB7 /* OverrideStored+CoreDataClass.swift */,
 				DDE179512C910127003CDDB7 /* OverrideStored+CoreDataProperties.swift */,
+				DDE1793E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift */,
+				DDE1793F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift */,
+				DDE179402C910127003CDDB7 /* StatsData+CoreDataClass.swift */,
+				DDE179412C910127003CDDB7 /* StatsData+CoreDataProperties.swift */,
+				DDE179482C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift */,
+				DDE179492C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift */,
+				DDE1793C2C910127003CDDB7 /* TempTargets+CoreDataClass.swift */,
+				DDE1793D2C910127003CDDB7 /* TempTargets+CoreDataProperties.swift */,
+				DDE1794A2C910127003CDDB7 /* TempTargetsSlider+CoreDataClass.swift */,
+				DDE1794B2C910127003CDDB7 /* TempTargetsSlider+CoreDataProperties.swift */,
 			);
 			path = "Classes+Properties";
 			sourceTree = "<group>";
@@ -3627,6 +3634,7 @@
 				6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */,
 				DD17454B2C55C62800211FAC /* AutosensSettingsRootView.swift in Sources */,
 				DDF847DF2C5C28780049BB3B /* LiveActivitySettingsProvider.swift in Sources */,
+				DDB37CC52D05048F00D99BF4 /* ContactTrickStorage.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
 				BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */,
 				BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */,

+ 33 - 0
FreeAPS/Sources/APS/Storage/ContactTrickStorage.swift

@@ -0,0 +1,33 @@
+import CoreData
+import Foundation
+import Swinject
+
+protocol ContactTrickStorage {
+    func fetchContactTrickEntryIds() async -> [NSManagedObjectID]
+    func storeContactTrickEntry(_ entry: ContactTrickEntry) async
+    func deleteContactTrickEntry(_ objectID: NSManagedObjectID) async
+}
+
+final class BaseContactTrickStorage: ContactTrickStorage, Injectable {
+    @Injected() private var settingsManager: SettingsManager!
+
+    private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
+    private let backgroundContext = CoreDataStack.shared.newTaskContext()
+
+    init(resolver: Resolver) {
+        injectServices(resolver)
+    }
+
+    func fetchContactTrickEntryIds() async -> [NSManagedObjectID] {
+        // TODO: implement
+        []
+    }
+
+    func storeContactTrickEntry(_: ContactTrickEntry) async {
+        // TODO: implement
+    }
+
+    func deleteContactTrickEntry(_: NSManagedObjectID) async {
+        // TODO: implement
+    }
+}

+ 111 - 60
FreeAPS/Sources/Models/ContactTrickEntry.swift

@@ -1,6 +1,6 @@
 import SwiftUI
 
-struct ContactTrickEntry: Hashable {
+struct ContactTrickEntry: Hashable, Sendable {
     var layout: ContactTrickLayout = .single
     var ring1: ContactTrickLargeRing = .none
     var primary: ContactTrickValue = .glucose
@@ -8,14 +8,34 @@ struct ContactTrickEntry: Hashable {
     var bottom: ContactTrickValue = .none
     var contactId: String? = nil
     var darkMode: Bool = true
-    var ringWidth: ringWidth = .regular
-    var ringGap: ringGap = .small
-    var fontSize: fontSize = .regular
-    var secondaryFontSize: fontSize = .small
+    var ringWidth: RingWidth = .regular
+    var ringGap: RingGap = .small
+    var fontSize: FontSize = .regular
+    var secondaryFontSize: FontSize = .small
     var fontWeight: Font.Weight = .medium
     var fontWidth: Font.Width = .standard
 
-    enum fontSize: Int {
+    // Convert `fontWeight` to a String for Core Data storage
+    var fontWeightString: String {
+        fontWeight.asString
+    }
+
+    // Initialize `fontWeight` from a String
+    static func fontWeight(from string: String) -> Font.Weight {
+        Font.Weight.fromString(string)
+    }
+
+    // Convert `fontWidth` to a String for Core Data storage
+    var fontWidthString: String {
+        fontWidth.asString
+    }
+
+    // Initialize `fontWidth` from a String
+    static func fontWidth(from string: String) -> Font.Width {
+        Font.Width.fromString(string)
+    }
+
+    enum FontSize: Int, Sendable {
         case tiny = 200
         case small = 250
         case regular = 300
@@ -31,7 +51,7 @@ struct ContactTrickEntry: Hashable {
         }
     }
 
-    enum ringWidth: Int {
+    enum RingWidth: Int, Sendable {
         case tiny = 3
         case small = 5
         case regular = 7
@@ -49,7 +69,7 @@ struct ContactTrickEntry: Hashable {
         }
     }
 
-    enum ringGap: Int {
+    enum RingGap: Int, Sendable {
         case tiny = 1
         case small = 2
         case regular = 3
@@ -68,58 +88,89 @@ struct ContactTrickEntry: Hashable {
     }
 }
 
-protocol ContactTrickObserver {
+// TODO: is this required?
+protocol ContactTrickObserver: Sendable {
     func basalProfileDidChange(_ entry: [ContactTrickEntry])
 }
 
-//
-// extension ContactTrickEntry {
-//    private enum CodingKeys: String, CodingKey {
-//        case layout
-//        case ring1
-//        case primary
-//        case top
-//        case bottom
-//        case contactId
-//        case darkMode
-//        case ringWidth
-//        case ringGap
-//        case fontSize
-//        case secondaryFontSize
-//        case fontWeight
-//        case fontWidth
-//    }
-//
-//    init(from decoder: Decoder) throws {
-//        let container = try decoder.container(keyedBy: CodingKeys.self)
-//        let layout = try container.decodeIfPresent(ContactTrickLayout.self, forKey: .layout) ?? .single
-//        let ring1 = try container.decodeIfPresent(ContactTrickLargeRing.self, forKey: .ring1) ?? .none
-//        let primary = try container.decodeIfPresent(ContactTrickValue.self, forKey: .primary) ?? .glucose
-//        let top = try container.decodeIfPresent(ContactTrickValue.self, forKey: .top) ?? .none
-//        let bottom = try container.decodeIfPresent(ContactTrickValue.self, forKey: .bottom) ?? .none
-//        let contactId = try container.decodeIfPresent(String.self, forKey: .contactId)
-//        let darkMode = try container.decodeIfPresent(Bool.self, forKey: .darkMode) ?? true
-//        let ringWidth = try container.decodeIfPresent(Int.self, forKey: .ringWidth) ?? 7
-//        let ringGap = try container.decodeIfPresent(Int.self, forKey: .ringGap) ?? 2
-//        let fontSize = try container.decodeIfPresent(Int.self, forKey: .fontSize) ?? 300
-//        let secondaryFontSize = try container.decodeIfPresent(Int.self, forKey: .secondaryFontSize) ?? 250
-//        let fontWeight = try container.decodeIfPresent(Font.Weight.self, forKey: .fontWeight) ?? .medium
-//        let fontWidth = try container.decodeIfPresent(Font.Width.self, forKey: .fontWidth) ?? .standard
-//
-//        self = ContactTrickEntry(
-//            layout: layout,
-//            ring1: ring1,
-//            primary: primary,
-//            top: top,
-//            bottom: bottom,
-//            contactId: contactId,
-//            darkMode: darkMode,
-//            ringWidth: ringWidth,
-//            ringGap: ringGap,
-//            fontSize: fontSize,
-//            secondaryFontSize: secondaryFontSize,
-//            fontWeight: fontWeight,
-//            fontWidth: fontWidth
-//        )
-//    }
-// }
+extension Font.Weight {
+    var displayName: String {
+        switch self {
+        case .light: return "Light"
+        case .regular: return "Regular"
+        case .medium: return "Medium"
+        case .bold: return "Bold"
+        default: return "Unknown"
+        }
+    }
+
+    private static let stringToFontWeight: [String: Font.Weight] = [
+        "ultraLight": .ultraLight,
+        "thin": .thin,
+        "light": .light,
+        "regular": .regular,
+        "medium": .medium,
+        "semibold": .semibold,
+        "bold": .bold,
+        "heavy": .heavy,
+        "black": .black
+    ]
+
+    private static let fontWeightToString: [Font.Weight: String] = [
+        .ultraLight: "ultraLight",
+        .thin: "thin",
+        .light: "light",
+        .regular: "regular",
+        .medium: "medium",
+        .semibold: "semibold",
+        .bold: "bold",
+        .heavy: "heavy",
+        .black: "black"
+    ]
+
+    /// Initialize `Font.Weight` from a string
+    static func fromString(_ string: String) -> Font.Weight {
+        stringToFontWeight[string] ?? .regular // Default fallback
+    }
+
+    /// Convert `Font.Weight` to a string
+    var asString: String {
+        Font.Weight.fontWeightToString[self] ?? "regular" // Default fallback
+    }
+}
+
+extension Font.Width {
+    var displayName: String {
+        switch self {
+        case .condensed: return "Condensed"
+        case .expanded: return "Expanded"
+        case .compressed: return "Compressed"
+        case .standard: return "Standard"
+        default: return "Unknown"
+        }
+    }
+
+    private static let stringToFontWidth: [String: Font.Width] = [
+        "compressed": .compressed,
+        "condensed": .condensed,
+        "standard": .standard,
+        "expanded": .expanded
+    ]
+
+    private static let fontWidthToString: [Font.Width: String] = [
+        .compressed: "compressed",
+        .condensed: "condensed",
+        .standard: "standard",
+        .expanded: "expanded"
+    ]
+
+    /// Initialize `Font.Width` from a string
+    static func fromString(_ string: String) -> Font.Width {
+        stringToFontWidth[string] ?? .standard // Default fallback
+    }
+
+    /// Convert `Font.Width` to a string
+    var asString: String {
+        Font.Width.fontWidthToString[self] ?? "standard" // Default fallback
+    }
+}

+ 8 - 32
FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickRootView.swift

@@ -271,10 +271,10 @@ extension ContactTrick {
                         ) {
                             ForEach(
                                 [
-                                    ContactTrickEntry.fontSize.tiny,
-                                    ContactTrickEntry.fontSize.small,
-                                    ContactTrickEntry.fontSize.regular,
-                                    ContactTrickEntry.fontSize.large
+                                    ContactTrickEntry.FontSize.tiny,
+                                    ContactTrickEntry.FontSize.small,
+                                    ContactTrickEntry.FontSize.regular,
+                                    ContactTrickEntry.FontSize.large
                                 ],
                                 id: \.self
                             ) { size in
@@ -287,10 +287,10 @@ extension ContactTrick {
                         ) {
                             ForEach(
                                 [
-                                    ContactTrickEntry.fontSize.tiny,
-                                    ContactTrickEntry.fontSize.small,
-                                    ContactTrickEntry.fontSize.regular,
-                                    ContactTrickEntry.fontSize.large
+                                    ContactTrickEntry.FontSize.tiny,
+                                    ContactTrickEntry.FontSize.small,
+                                    ContactTrickEntry.FontSize.regular,
+                                    ContactTrickEntry.FontSize.large
                                 ],
                                 id: \.self
                             ) { size in
@@ -377,27 +377,3 @@ extension ContactTrick {
         }
     }
 }
-
-extension Font.Width {
-    var displayName: String {
-        switch self {
-        case .condensed: return "Condensed"
-        case .expanded: return "Expanded"
-        case .compressed: return "Compressed"
-        case .standard: return "Standard"
-        default: return "Unknown"
-        }
-    }
-}
-
-extension Font.Weight {
-    var displayName: String {
-        switch self {
-        case .light: return "Light"
-        case .regular: return "Regular"
-        case .medium: return "Medium"
-        case .bold: return "Bold"
-        default: return "Unknown"
-        }
-    }
-}

+ 1 - 1
FreeAPS/Sources/Services/ContactTrick/ContactPicture.swift

@@ -619,7 +619,7 @@ struct ContactPicture_Previews: PreviewProvider {
     struct Preview: View {
         @State var rangeIndicator: Bool = true
         @State var darkMode: Bool = true
-        @State var fontSize: ContactTrickEntry.fontSize = .small
+        @State var fontSize: ContactTrickEntry.FontSize = .small
         @State var fontWeight: UIFont.Weight = .bold
         @State var fontName: String? = "AmericanTypewriter"
 

+ 4 - 0
Model/Classes+Properties/ContactTrickEntryStored+CoreDataClass.swift

@@ -0,0 +1,4 @@
+import CoreData
+import Foundation
+
+@objc(ContactTrickEntryStored) public class ContactTrickEntryStored: NSManagedObject {}

+ 23 - 0
Model/Classes+Properties/ContactTrickEntryStored+CoreDataProperties.swift

@@ -0,0 +1,23 @@
+import CoreData
+import Foundation
+
+public extension ContactTrickEntryStored {
+    @nonobjc class func fetchRequest() -> NSFetchRequest<ContactTrickEntryStored> {
+        NSFetchRequest<ContactTrickEntryStored>(entityName: "ContactTrickEntryStored")
+    }
+
+    @NSManaged var layout: String?
+    @NSManaged var ring1: String?
+    @NSManaged var primary: String?
+    @NSManaged var top: String?
+    @NSManaged var bottom: String?
+    @NSManaged var contactId: String?
+    @NSManaged var isDarkMode: Bool
+    @NSManaged var ringWidth: Int16
+    @NSManaged var ringGap: Int16
+    @NSManaged var id: UUID?
+    @NSManaged var fontSize: Int16
+    @NSManaged var fontSizeSecondary: Int16
+    @NSManaged var fontWidth: String?
+    @NSManaged var fontWeight: String?
+}

+ 17 - 1
Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23231" systemVersion="24A348" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23231" systemVersion="24B91" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
     <entity name="BolusStored" representedClassName="BolusStored" syncable="YES">
         <attribute name="amount" optional="YES" attributeType="Decimal" defaultValueString="0"/>
         <attribute name="isExternal" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
@@ -25,6 +25,22 @@
             <fetchIndexElement property="isFPU" type="Binary" order="ascending"/>
         </fetchIndex>
     </entity>
+    <entity name="ContactTrickEntryStored" representedClassName="ContactTrickEntryStored" syncable="YES" codeGenerationType="class">
+        <attribute name="bottom" optional="YES" attributeType="String"/>
+        <attribute name="contactId" optional="YES" attributeType="String"/>
+        <attribute name="fontSize" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="fontSizeSecondary" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="fontWeight" optional="YES" attributeType="String"/>
+        <attribute name="fontWidth" optional="YES" attributeType="String"/>
+        <attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
+        <attribute name="isDarkMode" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
+        <attribute name="layout" optional="YES" attributeType="String"/>
+        <attribute name="primary" optional="YES" attributeType="String"/>
+        <attribute name="ring1" optional="YES" attributeType="String"/>
+        <attribute name="ringGap" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="ringWidth" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="top" optional="YES" attributeType="String"/>
+    </entity>
     <entity name="Forecast" representedClassName="Forecast" syncable="YES">
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>