Skip to content

Commit d98af8f

Browse files
committed
Refactoring
1 parent cf5e01b commit d98af8f

File tree

10 files changed

+221
-221
lines changed

10 files changed

+221
-221
lines changed

LoopFollow.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
DD9ED0CA2D355257000D2A63 /* LogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ED0C92D355256000D2A63 /* LogView.swift */; };
8989
DD9ED0CC2D35526E000D2A63 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ED0CB2D35526E000D2A63 /* SearchBar.swift */; };
9090
DD9ED0CE2D35587A000D2A63 /* LogEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ED0CD2D355879000D2A63 /* LogEntry.swift */; };
91+
DDA9ACA82D6A66E200E6F1A9 /* ContactColorOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9ACA72D6A66DD00E6F1A9 /* ContactColorOption.swift */; };
92+
DDA9ACAA2D6A6B8300E6F1A9 /* ContactIncludeOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9ACA92D6A6B8200E6F1A9 /* ContactIncludeOption.swift */; };
93+
DDA9ACAC2D6B317100E6F1A9 /* ContactType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9ACAB2D6B316F00E6F1A9 /* ContactType.swift */; };
9194
DDAD162F2D2EF9830084BE10 /* RileyLinkHeartbeatBluetoothDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD162E2D2EF97C0084BE10 /* RileyLinkHeartbeatBluetoothDevice.swift */; };
9295
DDB0AF522BB1A8BE00AFA48B /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB0AF512BB1A8BE00AFA48B /* BuildDetails.swift */; };
9396
DDB0AF552BB1B24A00AFA48B /* BuildDetails.plist in Resources */ = {isa = PBXBuildFile; fileRef = DDB0AF542BB1B24A00AFA48B /* BuildDetails.plist */; };
@@ -362,6 +365,9 @@
362365
DD9ED0C92D355256000D2A63 /* LogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogView.swift; sourceTree = "<group>"; };
363366
DD9ED0CB2D35526E000D2A63 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
364367
DD9ED0CD2D355879000D2A63 /* LogEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEntry.swift; sourceTree = "<group>"; };
368+
DDA9ACA72D6A66DD00E6F1A9 /* ContactColorOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactColorOption.swift; sourceTree = "<group>"; };
369+
DDA9ACA92D6A6B8200E6F1A9 /* ContactIncludeOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactIncludeOption.swift; sourceTree = "<group>"; };
370+
DDA9ACAB2D6B316F00E6F1A9 /* ContactType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactType.swift; sourceTree = "<group>"; };
365371
DDAD162E2D2EF97C0084BE10 /* RileyLinkHeartbeatBluetoothDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkHeartbeatBluetoothDevice.swift; sourceTree = "<group>"; };
366372
DDB0AF502BB1A84500AFA48B /* capture-build-details.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "capture-build-details.sh"; sourceTree = "<group>"; };
367373
DDB0AF512BB1A8BE00AFA48B /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = "<group>"; };
@@ -706,6 +712,9 @@
706712
DD50C74D2D0828250057AE6F /* Contact */ = {
707713
isa = PBXGroup;
708714
children = (
715+
DDA9ACAB2D6B316F00E6F1A9 /* ContactType.swift */,
716+
DDA9ACA92D6A6B8200E6F1A9 /* ContactIncludeOption.swift */,
717+
DDA9ACA72D6A66DD00E6F1A9 /* ContactColorOption.swift */,
709718
DD50C7512D0828B40057AE6F /* Settings */,
710719
DD50C7542D0862770057AE6F /* ContactImageUpdater.swift */,
711720
);
@@ -1437,6 +1446,7 @@
14371446
FC16A97B249966A3003D6245 /* AlarmSound.swift in Sources */,
14381447
DDBE3ABD2CB5A961006B37DC /* OverrideView.swift in Sources */,
14391448
DDB0AF522BB1A8BE00AFA48B /* BuildDetails.swift in Sources */,
1449+
DDA9ACAA2D6A6B8300E6F1A9 /* ContactIncludeOption.swift in Sources */,
14401450
DD0C0C622C4175FD00DBADDF /* NSProfile.swift in Sources */,
14411451
DD58171E2D299FCA0041FB98 /* BluetoothDeviceDelegate.swift in Sources */,
14421452
DDE69ED22C7256260013EAEC /* RemoteType.swift in Sources */,
@@ -1456,6 +1466,7 @@
14561466
FCFEECA02488157B00402A7F /* Chart.swift in Sources */,
14571467
DDCF979424C0D380002C9752 /* UIViewExtension.swift in Sources */,
14581468
DD0C0C6D2C48606200DBADDF /* CarbMetric.swift in Sources */,
1469+
DDA9ACA82D6A66E200E6F1A9 /* ContactColorOption.swift in Sources */,
14591470
DD7E19882ACDA5DA00DBD158 /* Notes.swift in Sources */,
14601471
FCEF87AC24A141A700AE6FA0 /* Localizer.swift in Sources */,
14611472
FC1BDD3224A2585C001B652C /* DataStructs.swift in Sources */,
@@ -1518,6 +1529,7 @@
15181529
DD4878032C7B297E0048F05C /* StorageValue.swift in Sources */,
15191530
DD4878192C7C56D60048F05C /* TrioNightscoutRemoteController.swift in Sources */,
15201531
FC1BDD2B24A22650001B652C /* Stats.swift in Sources */,
1532+
DDA9ACAC2D6B317100E6F1A9 /* ContactType.swift in Sources */,
15211533
DDDF6F432D479A9900884336 /* LoopNightscoutRemoteView.swift in Sources */,
15221534
DDD10F052C529DA200D76A8E /* ObservableValue.swift in Sources */,
15231535
FC1BDD2D24A23204001B652C /* StatsView.swift in Sources */,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// ContactColorOption.swift
3+
// LoopFollow
4+
//
5+
// Created by Jonas Björkert on 2025-02-22.
6+
// Copyright © 2025 Jon Fawcett. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
enum ContactColorOption: String, CaseIterable {
12+
case red, blue, cyan, green, yellow, orange, purple, white, black
13+
14+
var uiColor: UIColor {
15+
switch self {
16+
case .red: return .red
17+
case .blue: return .blue
18+
case .cyan: return .cyan
19+
case .green: return .green
20+
case .yellow: return .yellow
21+
case .orange: return .orange
22+
case .purple: return .purple
23+
case .white: return .white
24+
case .black: return .black
25+
}
26+
}
27+
}

LoopFollow/Contact/ContactImageUpdater.swift

Lines changed: 100 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -14,61 +14,47 @@ class ContactImageUpdater {
1414
private let contactStore = CNContactStore()
1515
private let queue = DispatchQueue(label: "ContactImageUpdaterQueue")
1616

17-
//convert the saved strings to UI Color
18-
private var savedBackgroundUIColor: UIColor {
19-
switch ObservableUserDefaults.shared.contactBackgroundColor.value {
20-
case "red": return .red
21-
case "blue": return .blue
22-
case "cyan": return .cyan
23-
case "green": return .green
24-
case "yellow": return .yellow
25-
case "orange": return .orange
26-
case "purple": return .purple
27-
case "white": return .white
28-
case "black": return .black
29-
default: return .black
30-
}
31-
}
32-
33-
private var savedTextUIColor: UIColor {
34-
switch ObservableUserDefaults.shared.contactTextColor.value {
35-
case "red": return .red
36-
case "blue": return .blue
37-
case "cyan": return .cyan
38-
case "green": return .green
39-
case "yellow": return .yellow
40-
case "orange": return .orange
41-
case "purple": return .purple
42-
case "white": return .white
43-
case "black": return .black
44-
default: return .white
45-
}
46-
}
17+
private var savedBackgroundUIColor: UIColor {
18+
let rawValue = Storage.shared.contactBackgroundColor.value
19+
return ContactColorOption(rawValue: rawValue)?.uiColor ?? .black
20+
}
4721

48-
func updateContactImage(bgValue: String, extra: String, extraTrend: String, extraDelta: String, stale: Bool) {
49-
let contactSuffixes = ["BG", "Trend", "Delta"]
22+
private var savedTextUIColor: UIColor {
23+
let rawValue = Storage.shared.contactTextColor.value
24+
return ContactColorOption(rawValue: rawValue)?.uiColor ?? .white
25+
}
26+
27+
func updateContactImage(bgValue: String, trend: String, delta: String, stale: Bool) {
5028
queue.async {
5129
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
52-
print("Access to contacts is not authorized.")
30+
LogManager.shared.log(category: .contact, message: "Access to contacts is not authorized.")
5331
return
5432
}
55-
33+
5634
let bundleDisplayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "LoopFollow"
57-
58-
for suffix in contactSuffixes {
59-
let contactName = "\(bundleDisplayName) - \(suffix)"
60-
let contactType = suffix
61-
guard let imageData = self.generateContactImage(bgValue: bgValue, extra: extra, extraTrend: extraTrend, extraDelta: extraDelta, stale: stale, contactType: contactType)?.pngData() else {
62-
print("Failed to generate contact image for \(contactName).")
35+
36+
for contactType in ContactType.allCases {
37+
if contactType == .Delta && Storage.shared.contactDelta.value != .separate {
6338
continue
6439
}
65-
40+
41+
if contactType == .Trend && Storage.shared.contactTrend.value != .separate {
42+
continue
43+
}
44+
45+
let contactName = "\(bundleDisplayName) - \(contactType.rawValue)"
46+
47+
guard let imageData = self.generateContactImage(bgValue: bgValue, trend: trend, delta: delta, stale: stale, contactType: contactType)?.pngData() else {
48+
LogManager.shared.log(category: .contact, message: "Failed to generate contact image for \(contactName).")
49+
continue
50+
}
51+
6652
let predicate = CNContact.predicateForContacts(matchingName: contactName)
6753
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactImageDataKey] as [CNKeyDescriptor]
68-
54+
6955
do {
7056
let contacts = try self.contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
71-
57+
7258
if let contact = contacts.first, let mutableContact = contact.mutableCopy() as? CNMutableContact {
7359
mutableContact.imageData = imageData
7460
let saveRequest = CNSaveRequest()
@@ -85,93 +71,87 @@ class ContactImageUpdater {
8571
print("New contact created with updated image for \(contactName).")
8672
}
8773
} catch {
88-
print("Failed to update or create contact for \(contactName): \(error)")
74+
LogManager.shared.log(category: .contact, message: "Failed to update or create contact for \(contactName): \(error)")
8975
}
9076
}
9177
}
9278
}
9379

94-
private func generateContactImage(bgValue: String, extra: String, extraTrend: String, extraDelta: String, stale: Bool, contactType: String) -> UIImage? {
95-
let size = CGSize(width: 300, height: 300)
96-
UIGraphicsBeginImageContextWithOptions(size, false, 0)
97-
guard let context = UIGraphicsGetCurrentContext() else { return nil }
80+
private func generateContactImage(bgValue: String, trend: String, delta: String, stale: Bool, contactType: ContactType) -> UIImage? {
81+
let size = CGSize(width: 300, height: 300)
82+
UIGraphicsBeginImageContextWithOptions(size, false, 0)
83+
guard let context = UIGraphicsGetCurrentContext() else { return nil }
9884

99-
savedBackgroundUIColor.setFill()
100-
context.fill(CGRect(origin: .zero, size: size))
85+
savedBackgroundUIColor.setFill()
86+
context.fill(CGRect(origin: .zero, size: size))
10187

102-
let paragraphStyle = NSMutableParagraphStyle()
103-
paragraphStyle.alignment = .center
88+
let paragraphStyle = NSMutableParagraphStyle()
89+
paragraphStyle.alignment = .center
10490

105-
let maxFontSize: CGFloat = extra.isEmpty ? 200 : 160
106-
let fontSize = maxFontSize - CGFloat(bgValue.count * 15)
107-
var bgAttributes: [NSAttributedString.Key: Any] = [
108-
.font: UIFont.boldSystemFont(ofSize: fontSize),
109-
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
110-
.paragraphStyle: paragraphStyle
111-
]
91+
// Format extraDelta based on the user's unit preference
92+
let unitPreference = UserDefaultsRepository.units.value
93+
let yOffset: CGFloat = 48
94+
if contactType == .Trend && Storage.shared.contactTrend.value == .separate {
95+
let trendRect = CGRect(x: 0, y: 46, width: size.width, height: size.height - 80)
96+
let trendFontSize = max(40, 200 - CGFloat(trend.count * 15))
11297

113-
if stale {
114-
// Force background color back to black if stale
115-
UIColor.black.setFill()
116-
context.fill(CGRect(origin: .zero, size: size))
117-
bgAttributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
118-
}
98+
let trendAttributes: [NSAttributedString.Key: Any] = [
99+
.font: UIFont.boldSystemFont(ofSize: trendFontSize),
100+
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
101+
.paragraphStyle: paragraphStyle
102+
]
119103

120-
let extraAttributes: [NSAttributedString.Key: Any] = [
121-
.font: UIFont.systemFont(ofSize: 90),
122-
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
123-
.paragraphStyle: paragraphStyle
124-
]
125-
126-
let trendFontSize = max(40, 200 - CGFloat(extraTrend.count * 15))
127-
let deltaFontSize = max(40, 200 - CGFloat(extraDelta.count * 15))
128-
129-
let trendAttributes: [NSAttributedString.Key: Any] = [
130-
.font: UIFont.boldSystemFont(ofSize: trendFontSize),
131-
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
132-
.paragraphStyle: paragraphStyle
133-
]
134-
135-
let deltaAttributes: [NSAttributedString.Key: Any] = [
136-
.font: UIFont.boldSystemFont(ofSize: deltaFontSize),
137-
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
138-
.paragraphStyle: paragraphStyle
139-
]
140-
141-
// Format extraDelta based on the user's unit preference
142-
let unitPreference = UserDefaultsRepository.units.value
143-
let formattedExtraDelta: String
144-
let yOffset: CGFloat = unitPreference == "mg/dL" ? 46 : 61 // Add 15 if mmol is selected
145-
if unitPreference == "mg/dL" {
146-
formattedExtraDelta = String(format: "%.0f", (extraDelta as NSString).doubleValue)
147-
} else {
148-
formattedExtraDelta = String(format: "%.1f", (extraDelta as NSString).doubleValue)
149-
}
150-
151-
if contactType == "Trend" && ObservableUserDefaults.shared.contactTrend.value == "Separate" {
152-
// Customizing image for Trend contact when value is Separate
153-
let trendRect = CGRect(x: 0, y: 46, width: size.width, height: size.height - 80)
154-
extraTrend.draw(in: trendRect, withAttributes: trendAttributes)
155-
} else if contactType == "Delta" && ObservableUserDefaults.shared.contactDelta.value == "Separate" {
156-
// Customizing image for Delta contact when value is Separate
157-
let deltaRect = CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)
158-
formattedExtraDelta.draw(in: deltaRect, withAttributes: deltaAttributes)
159-
} else if contactType == "BG" {
160-
// Customizing image for BG contact
161-
let bgRect = extra.isEmpty
162-
? CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)
163-
: CGRect(x: 0, y: yOffset - 20, width: size.width, height: size.height / 2)
164-
165-
bgValue.draw(in: bgRect, withAttributes: bgAttributes)
166-
167-
if !extra.isEmpty {
168-
let extraRect = CGRect(x: 0, y: size.height / 2 + 6, width: size.width, height: size.height / 2 - 20)
169-
extra.draw(in: extraRect, withAttributes: extraAttributes)
104+
trend.draw(in: trendRect, withAttributes: trendAttributes)
105+
} else if contactType == .Delta && Storage.shared.contactDelta.value == .separate {
106+
let deltaRect = CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)
107+
let deltaFontSize = max(40, 200 - CGFloat(delta.count * 15))
108+
109+
let deltaAttributes: [NSAttributedString.Key: Any] = [
110+
.font: UIFont.boldSystemFont(ofSize: deltaFontSize),
111+
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
112+
.paragraphStyle: paragraphStyle
113+
]
114+
115+
delta.draw(in: deltaRect, withAttributes: deltaAttributes)
116+
} else if contactType == .BG {
117+
let includesExtra = Storage.shared.contactDelta.value == .include || Storage.shared.contactTrend.value == .include
118+
119+
let maxFontSize: CGFloat = includesExtra ? 160 : 200
120+
let fontSize = maxFontSize - CGFloat(bgValue.count * 15)
121+
var bgAttributes: [NSAttributedString.Key: Any] = [
122+
.font: UIFont.boldSystemFont(ofSize: fontSize),
123+
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
124+
.paragraphStyle: paragraphStyle
125+
]
126+
127+
if stale {
128+
// Force background color back to black if stale
129+
UIColor.black.setFill()
130+
context.fill(CGRect(origin: .zero, size: size))
131+
bgAttributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
132+
}
133+
134+
let bgRect: CGRect = includesExtra
135+
? CGRect(x: 0, y: yOffset - 20, width: size.width, height: size.height / 2)
136+
: CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)
137+
138+
bgValue.draw(in: bgRect, withAttributes: bgAttributes)
139+
140+
if includesExtra {
141+
let extraRect = CGRect(x: 0, y: size.height / 2 + 6, width: size.width, height: size.height / 2 - 20)
142+
let extraAttributes: [NSAttributedString.Key: Any] = [
143+
.font: UIFont.systemFont(ofSize: 90),
144+
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
145+
.paragraphStyle: paragraphStyle
146+
]
147+
148+
let extra = Storage.shared.contactDelta.value == .include ? delta : trend
149+
extra.draw(in: extraRect, withAttributes: extraAttributes)
150+
}
170151
}
171-
}
172152

173-
let image = UIGraphicsGetImageFromCurrentImageContext()
174-
UIGraphicsEndImageContext()
175-
return image
176-
}
153+
let image = UIGraphicsGetImageFromCurrentImageContext()
154+
UIGraphicsEndImageContext()
155+
return image
156+
}
177157
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// ContactIncludeOption.swift
3+
// LoopFollow
4+
//
5+
// Created by Jonas Björkert on 2025-02-22.
6+
// Copyright © 2025 Jon Fawcett. All rights reserved.
7+
//
8+
9+
enum ContactIncludeOption: String, Codable, Equatable, CaseIterable {
10+
case off = "Off"
11+
case include = "Include"
12+
case separate = "Separate"
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// ContactSuffix.swift
3+
// LoopFollow
4+
//
5+
// Created by Jonas Björkert on 2025-02-23.
6+
// Copyright © 2025 Jon Fawcett. All rights reserved.
7+
//
8+
9+
enum ContactType: String, CaseIterable {
10+
case BG = "BG"
11+
case Trend = "Trend"
12+
case Delta = "Delta"
13+
}

0 commit comments

Comments
 (0)