ContactImageStorage.swift 8.4 KB

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