ContactImageStorage.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import CoreData
  2. import Foundation
  3. import SwiftUI
  4. import Swinject
  5. protocol ContactImageStorage {
  6. func fetchContactImageEntries() async -> [ContactImageEntry]
  7. func storeContactImageEntry(_ entry: ContactImageEntry) async
  8. func updateContactImageEntry(_ contactImageEntry: ContactImageEntry) async
  9. func deleteContactImageEntry(_ objectID: NSManagedObjectID) async
  10. }
  11. final class BaseContactImageStorage: ContactImageStorage, Injectable {
  12. @Injected() private var settingsManager: SettingsManager!
  13. private let makeContext: () -> NSManagedObjectContext
  14. init(resolver: Resolver, contextProvider: (() -> NSManagedObjectContext)? = nil) {
  15. makeContext = contextProvider ?? { CoreDataStack.shared.newTaskContext() }
  16. injectServices(resolver)
  17. }
  18. /// Fetches all stored Contact Trick entries.
  19. ///
  20. /// The method retrieves `ContactImageEntryStored` objects from Core Data, maps them to
  21. /// `ContactImageEntry` objects, and returns the results.
  22. ///
  23. /// - Returns: An array of `ContactImageEntry` objects.
  24. func fetchContactImageEntries() async -> [ContactImageEntry] {
  25. let context = makeContext()
  26. context.name = "fetchContactImageEntries"
  27. do {
  28. let results = try await CoreDataStack.shared.fetchEntitiesAsync(
  29. ofType: ContactImageEntryStored.self,
  30. onContext: context,
  31. predicate: NSPredicate.all,
  32. key: "hasHighContrast",
  33. ascending: false
  34. )
  35. return try await context.perform {
  36. guard let fetchedContactImageEntries = results as? [ContactImageEntryStored]
  37. else { throw CoreDataError.fetchError(function: #function, file: #file)
  38. }
  39. return fetchedContactImageEntries.compactMap { entry in
  40. ContactImageEntry(
  41. name: entry.name ?? String(localized: "No name provided"),
  42. layout: ContactImageLayout(rawValue: entry.layout ?? "Default") ?? .default,
  43. ring: ContactImageLargeRing(rawValue: entry.ring ?? "Hidden") ?? .none,
  44. primary: ContactImageValue(rawValue: entry.primary ?? "Glucose Reading") ?? .glucose,
  45. top: ContactImageValue(rawValue: entry.top ?? "None") ?? .none,
  46. bottom: ContactImageValue(rawValue: entry.bottom ?? "None") ?? .none,
  47. contactId: entry.contactId?.string,
  48. hasHighContrast: entry.hasHighContrast,
  49. ringWidth: ContactImageEntry.RingWidth(rawValue: Int(entry.ringWidth)) ?? .regular,
  50. ringGap: ContactImageEntry.RingGap(rawValue: Int(entry.ringGap)) ?? .small,
  51. colorMode: ContactImageEntry.ColorMode(rawValue: entry.colorMode ?? "Color") ?? .color,
  52. fontSize: ContactImageEntry.FontSize(rawValue: Int(entry.fontSize)) ?? .regular,
  53. secondaryFontSize: ContactImageEntry.FontSize(rawValue: Int(entry.fontSizeSecondary)) ?? .small,
  54. fontWeight: Font.Weight.fromString(entry.fontWeight ?? "regular"),
  55. fontWidth: Font.Width.fromString(entry.fontWidth ?? "standard"),
  56. managedObjectID: entry.objectID
  57. )
  58. }
  59. }
  60. } catch {
  61. debug(.default, "\(DebuggingIdentifiers.failed) Error fetching contact image entries: \(error)")
  62. return []
  63. }
  64. }
  65. /// Stores a new Contact Trick entry.
  66. ///
  67. /// This method creates a new `ContactImageEntryStored` object in the background context,
  68. /// populates its properties with the values from the provided `ContactImageEntry`, and
  69. /// saves the context if changes exist.
  70. ///
  71. /// - Parameter contactImageEntry: The `ContactImageEntry` object to be stored.
  72. func storeContactImageEntry(_ contactImageEntry: ContactImageEntry) async {
  73. let context = makeContext()
  74. context.name = "storeContactImageEntry"
  75. await context.perform {
  76. let newContactImageEntry = ContactImageEntryStored(context: context)
  77. newContactImageEntry.id = UUID()
  78. newContactImageEntry.name = contactImageEntry.name
  79. newContactImageEntry.contactId = contactImageEntry.contactId
  80. newContactImageEntry.layout = contactImageEntry.layout.rawValue
  81. newContactImageEntry.ring = contactImageEntry.ring.rawValue
  82. newContactImageEntry.primary = contactImageEntry.primary.rawValue
  83. newContactImageEntry.top = contactImageEntry.top.rawValue
  84. newContactImageEntry.bottom = contactImageEntry.bottom.rawValue
  85. newContactImageEntry.hasHighContrast = contactImageEntry.hasHighContrast
  86. newContactImageEntry.ringWidth = Int16(contactImageEntry.ringWidth.rawValue)
  87. newContactImageEntry.ringGap = Int16(contactImageEntry.ringGap.rawValue)
  88. newContactImageEntry.colorMode = contactImageEntry.colorMode.rawValue
  89. newContactImageEntry.fontSize = Int16(contactImageEntry.fontSize.rawValue)
  90. newContactImageEntry.fontSizeSecondary = Int16(contactImageEntry.secondaryFontSize.rawValue)
  91. newContactImageEntry.fontWidth = contactImageEntry.fontWidth.asString
  92. newContactImageEntry.fontWeight = contactImageEntry.fontWeight.asString
  93. do {
  94. guard context.hasChanges else { return }
  95. try context.save()
  96. } catch let error as NSError {
  97. debugPrint(
  98. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Contact Trick Entry to Core Data with error: \(error.userInfo)"
  99. )
  100. }
  101. }
  102. }
  103. /// Updates an existing Contact Trick entry in Core Data.
  104. ///
  105. /// This method finds the existing `ContactImageEntryStored` object by its `contactId` and updates
  106. /// its properties with the values from the provided `ContactImageEntry`. If no matching entry exists,
  107. /// it does nothing.
  108. ///
  109. /// - Parameter contactImageEntry: The `ContactImageEntry` object with updated values.
  110. func updateContactImageEntry(_ contactImageEntry: ContactImageEntry) async {
  111. let context = makeContext()
  112. context.name = "updateContactImageEntry"
  113. await context.perform {
  114. let fetchRequest: NSFetchRequest<ContactImageEntryStored> = ContactImageEntryStored.fetchRequest()
  115. fetchRequest.predicate = NSPredicate(format: "contactId == %@", contactImageEntry.contactId ?? "")
  116. do {
  117. if let existingEntry = try context.fetch(fetchRequest).first {
  118. // Update the properties of the existing entry
  119. existingEntry.name = contactImageEntry.name
  120. existingEntry.layout = contactImageEntry.layout.rawValue
  121. existingEntry.ring = contactImageEntry.ring.rawValue
  122. existingEntry.primary = contactImageEntry.primary.rawValue
  123. existingEntry.top = contactImageEntry.top.rawValue
  124. existingEntry.bottom = contactImageEntry.bottom.rawValue
  125. existingEntry.hasHighContrast = contactImageEntry.hasHighContrast
  126. existingEntry.ringWidth = Int16(contactImageEntry.ringWidth.rawValue)
  127. existingEntry.ringGap = Int16(contactImageEntry.ringGap.rawValue)
  128. existingEntry.colorMode = contactImageEntry.colorMode.rawValue
  129. existingEntry.fontSize = Int16(contactImageEntry.fontSize.rawValue)
  130. existingEntry.fontSizeSecondary = Int16(contactImageEntry.secondaryFontSize.rawValue)
  131. existingEntry.fontWeight = contactImageEntry.fontWeight.asString
  132. existingEntry.fontWidth = contactImageEntry.fontWidth.asString
  133. guard context.hasChanges else { return }
  134. try context.save()
  135. } else {
  136. debugPrint(
  137. "\(DebuggingIdentifiers.failed) \(#file) \(#function) No matching Contact Trick Entry found to update."
  138. )
  139. }
  140. } catch let error as NSError {
  141. debugPrint(
  142. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update Contact Trick Entry with error: \(error.userInfo)"
  143. )
  144. }
  145. }
  146. }
  147. /// Deletes a Contact Trick entry from Core Data.
  148. ///
  149. /// - Parameter objectID: The `NSManagedObjectID` of the object to delete.
  150. func deleteContactImageEntry(_ objectID: NSManagedObjectID) async {
  151. await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
  152. }
  153. }