ContactImageUpdater.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. // LoopFollow
  2. // ContactImageUpdater.swift
  3. import Contacts
  4. import Foundation
  5. import UIKit
  6. class ContactImageUpdater {
  7. private let contactStore = CNContactStore()
  8. private let queue = DispatchQueue(label: "ContactImageUpdaterQueue")
  9. private var savedBackgroundUIColor: UIColor {
  10. let rawValue = Storage.shared.contactBackgroundColor.value
  11. return ContactColorOption(rawValue: rawValue)?.uiColor ?? .black
  12. }
  13. private var savedTextUIColor: UIColor {
  14. let rawValue = Storage.shared.contactTextColor.value
  15. return ContactColorOption(rawValue: rawValue)?.uiColor ?? .white
  16. }
  17. func updateContactImage(bgValue: String, trend: String, delta: String, stale: Bool) {
  18. queue.async {
  19. guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
  20. LogManager.shared.log(category: .contact, message: "Access to contacts is not authorized.")
  21. return
  22. }
  23. let bundleDisplayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "LoopFollow"
  24. for contactType in ContactType.allCases {
  25. if contactType == .Delta, Storage.shared.contactDelta.value != .separate {
  26. continue
  27. }
  28. if contactType == .Trend, Storage.shared.contactTrend.value != .separate {
  29. continue
  30. }
  31. let contactName = "\(bundleDisplayName) - \(contactType.rawValue)"
  32. guard let imageData = self.generateContactImage(bgValue: bgValue, trend: trend, delta: delta, stale: stale, contactType: contactType)?.pngData() else {
  33. LogManager.shared.log(category: .contact, message: "Failed to generate contact image for \(contactName).")
  34. continue
  35. }
  36. let predicate = CNContact.predicateForContacts(matchingName: contactName)
  37. let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactImageDataKey] as [CNKeyDescriptor]
  38. do {
  39. let contacts = try self.contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
  40. if let contact = contacts.first, let mutableContact = contact.mutableCopy() as? CNMutableContact {
  41. mutableContact.imageData = imageData
  42. let saveRequest = CNSaveRequest()
  43. saveRequest.update(mutableContact)
  44. try self.contactStore.execute(saveRequest)
  45. print("Contact image updated successfully for \(contactName).")
  46. } else {
  47. let newContact = CNMutableContact()
  48. newContact.givenName = contactName
  49. newContact.imageData = imageData
  50. let saveRequest = CNSaveRequest()
  51. saveRequest.add(newContact, toContainerWithIdentifier: nil)
  52. try self.contactStore.execute(saveRequest)
  53. print("New contact created with updated image for \(contactName).")
  54. }
  55. } catch {
  56. LogManager.shared.log(category: .contact, message: "Failed to update or create contact for \(contactName): \(error)")
  57. }
  58. }
  59. }
  60. }
  61. private func generateContactImage(bgValue: String, trend: String, delta: String, stale: Bool, contactType: ContactType) -> UIImage? {
  62. let size = CGSize(width: 300, height: 300)
  63. UIGraphicsBeginImageContextWithOptions(size, false, 0)
  64. guard let context = UIGraphicsGetCurrentContext() else { return nil }
  65. savedBackgroundUIColor.setFill()
  66. context.fill(CGRect(origin: .zero, size: size))
  67. let paragraphStyle = NSMutableParagraphStyle()
  68. paragraphStyle.alignment = .center
  69. // Format extraDelta based on the user's unit preference
  70. let unitPreference = Storage.shared.units.value
  71. let yOffset: CGFloat = 48
  72. if contactType == .Trend, Storage.shared.contactTrend.value == .separate {
  73. let trendRect = CGRect(x: 0, y: 46, width: size.width, height: size.height - 80)
  74. let trendFontSize = max(40, 200 - CGFloat(trend.count * 15))
  75. let trendAttributes: [NSAttributedString.Key: Any] = [
  76. .font: UIFont.boldSystemFont(ofSize: trendFontSize),
  77. .foregroundColor: stale ? UIColor.gray : savedTextUIColor,
  78. .paragraphStyle: paragraphStyle,
  79. ]
  80. trend.draw(in: trendRect, withAttributes: trendAttributes)
  81. } else if contactType == .Delta, Storage.shared.contactDelta.value == .separate {
  82. let deltaRect = CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)
  83. let deltaFontSize = max(40, 200 - CGFloat(delta.count * 15))
  84. let deltaAttributes: [NSAttributedString.Key: Any] = [
  85. .font: UIFont.boldSystemFont(ofSize: deltaFontSize),
  86. .foregroundColor: stale ? UIColor.gray : savedTextUIColor,
  87. .paragraphStyle: paragraphStyle,
  88. ]
  89. delta.draw(in: deltaRect, withAttributes: deltaAttributes)
  90. } else if contactType == .BG {
  91. let includesExtra = Storage.shared.contactDelta.value == .include || Storage.shared.contactTrend.value == .include
  92. let maxFontSize: CGFloat = includesExtra ? 160 : 200
  93. let fontSize = maxFontSize - CGFloat(bgValue.count * 15)
  94. var bgAttributes: [NSAttributedString.Key: Any] = [
  95. .font: UIFont.boldSystemFont(ofSize: fontSize),
  96. .foregroundColor: stale ? UIColor.gray : savedTextUIColor,
  97. .paragraphStyle: paragraphStyle,
  98. ]
  99. if stale {
  100. // Force background color back to black if stale
  101. UIColor.black.setFill()
  102. context.fill(CGRect(origin: .zero, size: size))
  103. bgAttributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
  104. }
  105. let bgRect: CGRect = includesExtra
  106. ? CGRect(x: 0, y: yOffset - 20, width: size.width, height: size.height / 2)
  107. : CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)
  108. bgValue.draw(in: bgRect, withAttributes: bgAttributes)
  109. if includesExtra {
  110. let extraRect = CGRect(x: 0, y: size.height / 2 + 6, width: size.width, height: size.height / 2 - 20)
  111. let extraAttributes: [NSAttributedString.Key: Any] = [
  112. .font: UIFont.systemFont(ofSize: 90),
  113. .foregroundColor: stale ? UIColor.gray : savedTextUIColor,
  114. .paragraphStyle: paragraphStyle,
  115. ]
  116. let extra = Storage.shared.contactDelta.value == .include ? delta : trend
  117. extra.draw(in: extraRect, withAttributes: extraAttributes)
  118. }
  119. }
  120. let image = UIGraphicsGetImageFromCurrentImageContext()
  121. UIGraphicsEndImageContext()
  122. return image
  123. }
  124. }