Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 67 additions & 69 deletions LoopFollow/Contact/ContactImageUpdater.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ class ContactImageUpdater {

let contactName = "\(bundleDisplayName) - \(contactType.rawValue)"

guard let imageData = self.generateContactImage(bgValue: bgValue, trend: trend, delta: delta, iob: iob, stale: stale, contactType: contactType)?.pngData() else {
let includedFields = self.getIncludedFields(for: contactType)

guard let imageData = self.generateContactImage(bgValue: bgValue, trend: trend, delta: delta, iob: iob, stale: stale, contactType: contactType, includedFields: includedFields)?.pngData() else {
LogManager.shared.log(category: .contact, message: "Failed to generate contact image for \(contactName).")
continue
}
Expand Down Expand Up @@ -104,7 +106,24 @@ class ContactImageUpdater {
}
}

private func generateContactImage(bgValue: String, trend: String, delta: String, iob: String, stale: Bool, contactType: ContactType) -> UIImage? {
private func getIncludedFields(for contactType: ContactType) -> [ContactType] {
var included: [ContactType] = []
if Storage.shared.contactTrend.value == .include,
Storage.shared.contactTrendTarget.value == contactType {
included.append(.Trend)
}
if Storage.shared.contactDelta.value == .include,
Storage.shared.contactDeltaTarget.value == contactType {
included.append(.Delta)
}
if Storage.shared.contactIOB.value == .include,
Storage.shared.contactIOBTarget.value == contactType {
included.append(.IOB)
}
return included
}

private func generateContactImage(bgValue: String, trend: String, delta: String, iob: String, stale: Bool, contactType: ContactType, includedFields: [ContactType]) -> UIImage? {
let size = CGSize(width: 300, height: 300)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
Expand All @@ -115,84 +134,63 @@ class ContactImageUpdater {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center

// Format extraDelta based on the user's unit preference
let unitPreference = Storage.shared.units.value
let yOffset: CGFloat = 48
if contactType == .Trend, Storage.shared.contactTrend.value == .separate {
let trendRect = CGRect(x: 0, y: 46, width: size.width, height: size.height - 80)
let trendFontSize = max(40, 200 - CGFloat(trend.count * 15))

let trendAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: trendFontSize),
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
.paragraphStyle: paragraphStyle,
]
// Get the primary value for this contact type
let primaryValue: String
switch contactType {
case .BG: primaryValue = bgValue
case .Trend: primaryValue = trend
case .Delta: primaryValue = delta
case .IOB: primaryValue = iob
}

trend.draw(in: trendRect, withAttributes: trendAttributes)
} else if contactType == .Delta, Storage.shared.contactDelta.value == .separate {
let deltaRect = CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)
let deltaFontSize = max(40, 200 - CGFloat(delta.count * 15))
// Build extra values from included fields
var extraValues: [String] = []
for field in includedFields {
switch field {
case .Trend: extraValues.append(trend)
case .Delta: extraValues.append(delta)
case .IOB: extraValues.append(iob)
case .BG: break
}
}

let deltaAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: deltaFontSize),
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
.paragraphStyle: paragraphStyle,
]
let hasExtras = !extraValues.isEmpty

delta.draw(in: deltaRect, withAttributes: deltaAttributes)
} else if contactType == .IOB, Storage.shared.contactIOB.value == .separate {
let iobRect = CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)
let iobFontSize = max(40, 200 - CGFloat(iob.count * 15))
// Determine font sizes based on number of extras
let maxFontSize: CGFloat = extraValues.count >= 2 ? 140 : (hasExtras ? 160 : 200)
let extraFontSize: CGFloat = extraValues.count >= 2 ? 60 : 90

let iobAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: iobFontSize),
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
.paragraphStyle: paragraphStyle,
]
let fontSize = max(40, maxFontSize - CGFloat(primaryValue.count * 15))

var primaryAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: fontSize),
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
.paragraphStyle: paragraphStyle,
]

if stale {
UIColor.black.setFill()
context.fill(CGRect(origin: .zero, size: size))
primaryAttributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
}

iob.draw(in: iobRect, withAttributes: iobAttributes)
} else if contactType == .BG {
let includesExtra = Storage.shared.contactDelta.value == .include || Storage.shared.contactTrend.value == .include || Storage.shared.contactIOB.value == .include
let primaryRect: CGRect = hasExtras
? CGRect(x: 0, y: yOffset - 20, width: size.width, height: size.height / 2)
: CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)

let maxFontSize: CGFloat = includesExtra ? 160 : 200
let fontSize = maxFontSize - CGFloat(bgValue.count * 15)
var bgAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: fontSize),
primaryValue.draw(in: primaryRect, withAttributes: primaryAttributes)

if hasExtras {
let extraString = extraValues.joined(separator: " ")
let extraRect = CGRect(x: 0, y: size.height / 2 + 6, width: size.width, height: size.height / 2 - 20)
let extraAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: extraFontSize),
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
.paragraphStyle: paragraphStyle,
]

if stale {
// Force background color back to black if stale
UIColor.black.setFill()
context.fill(CGRect(origin: .zero, size: size))
bgAttributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
}

let bgRect: CGRect = includesExtra
? CGRect(x: 0, y: yOffset - 20, width: size.width, height: size.height / 2)
: CGRect(x: 0, y: yOffset, width: size.width, height: size.height - 80)

bgValue.draw(in: bgRect, withAttributes: bgAttributes)

if includesExtra {
let extraRect = CGRect(x: 0, y: size.height / 2 + 6, width: size.width, height: size.height / 2 - 20)
let extraAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 90),
.foregroundColor: stale ? UIColor.gray : savedTextUIColor,
.paragraphStyle: paragraphStyle,
]

let extra: String
if Storage.shared.contactDelta.value == .include {
extra = delta
} else if Storage.shared.contactTrend.value == .include {
extra = trend
} else {
extra = iob
}
extra.draw(in: extraRect, withAttributes: extraAttributes)
}
extraString.draw(in: extraRect, withAttributes: extraAttributes)
}

let image = UIGraphicsGetImageFromCurrentImageContext()
Expand Down
2 changes: 1 addition & 1 deletion LoopFollow/Contact/ContactType.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// LoopFollow
// ContactType.swift

enum ContactType: String, CaseIterable {
enum ContactType: String, CaseIterable, Codable {
case BG
case Trend
case Delta
Expand Down
32 changes: 28 additions & 4 deletions LoopFollow/Settings/ContactSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ struct ContactSettingsView: View {
}

Section(header: Text("Additional Information")) {
Text("To see your trend, delta, or IOB, include one in the original '\(viewModel.contactName)' contact, or create separate contacts ending in '- Trend', '- Delta', or '- IOB' for up to four contacts on your watch face.")
Text("To see your trend, delta, or IOB, include them in another contact or create separate contacts. When using 'Include', select which contact to add the value to.")
.font(.footnote)
.foregroundColor(.secondary)
.padding(.vertical, 4)

Text("Show Trend")
Text("Trend")
.font(.subheadline)
Picker("Show Trend", selection: $viewModel.contactTrend) {
ForEach(ContactIncludeOption.allCases, id: \.self) { option in
Expand All @@ -64,7 +64,15 @@ struct ContactSettingsView: View {
}
.pickerStyle(SegmentedPickerStyle())

Text("Show Delta")
if viewModel.contactTrend == .include {
Picker("Include Trend in", selection: $viewModel.contactTrendTarget) {
ForEach(viewModel.availableTargets(for: .Trend), id: \.self) { target in
Text(target.rawValue).tag(target)
}
}
}

Text("Delta")
.font(.subheadline)
Picker("Show Delta", selection: $viewModel.contactDelta) {
ForEach(ContactIncludeOption.allCases, id: \.self) { option in
Expand All @@ -73,14 +81,30 @@ struct ContactSettingsView: View {
}
.pickerStyle(SegmentedPickerStyle())

Text("Show IOB")
if viewModel.contactDelta == .include {
Picker("Include Delta in", selection: $viewModel.contactDeltaTarget) {
ForEach(viewModel.availableTargets(for: .Delta), id: \.self) { target in
Text(target.rawValue).tag(target)
}
}
}

Text("IOB")
.font(.subheadline)
Picker("Show IOB", selection: $viewModel.contactIOB) {
ForEach(ContactIncludeOption.allCases, id: \.self) { option in
Text(option.rawValue).tag(option)
}
}
.pickerStyle(SegmentedPickerStyle())

if viewModel.contactIOB == .include {
Picker("Include IOB in", selection: $viewModel.contactIOBTarget) {
ForEach(viewModel.availableTargets(for: .IOB), id: \.self) { target in
Text(target.rawValue).tag(target)
}
}
}
}
}
}
Expand Down
80 changes: 68 additions & 12 deletions LoopFollow/Settings/ContactSettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,63 @@ class ContactSettingsViewModel: ObservableObject {

@Published var contactTrend: ContactIncludeOption {
didSet {
if contactTrend == .include {
if contactDelta == .include { contactDelta = .off }
if contactIOB == .include { contactIOB = .off }
}
Storage.shared.contactTrend.value = contactTrend
if contactTrend != .include {
contactTrendTarget = .BG
}
if contactTrend != .separate {
if contactDeltaTarget == .Trend { contactDeltaTarget = .BG }
if contactIOBTarget == .Trend { contactIOBTarget = .BG }
}
triggerRefresh()
}
}

@Published var contactDelta: ContactIncludeOption {
didSet {
if contactDelta == .include {
if contactTrend == .include { contactTrend = .off }
if contactIOB == .include { contactIOB = .off }
}
Storage.shared.contactDelta.value = contactDelta
if contactDelta != .include {
contactDeltaTarget = .BG
}
if contactDelta != .separate {
if contactTrendTarget == .Delta { contactTrendTarget = .BG }
if contactIOBTarget == .Delta { contactIOBTarget = .BG }
}
triggerRefresh()
}
}

@Published var contactIOB: ContactIncludeOption {
didSet {
if contactIOB == .include {
if contactTrend == .include { contactTrend = .off }
if contactDelta == .include { contactDelta = .off }
}
Storage.shared.contactIOB.value = contactIOB
if contactIOB != .include {
contactIOBTarget = .BG
}
if contactIOB != .separate {
if contactTrendTarget == .IOB { contactTrendTarget = .BG }
if contactDeltaTarget == .IOB { contactDeltaTarget = .BG }
}
triggerRefresh()
}
}

@Published var contactTrendTarget: ContactType {
didSet {
Storage.shared.contactTrendTarget.value = contactTrendTarget
triggerRefresh()
}
}

@Published var contactDeltaTarget: ContactType {
didSet {
Storage.shared.contactDeltaTarget.value = contactDeltaTarget
triggerRefresh()
}
}

@Published var contactIOBTarget: ContactType {
didSet {
Storage.shared.contactIOBTarget.value = contactIOBTarget
triggerRefresh()
}
}
Expand All @@ -77,6 +107,9 @@ class ContactSettingsViewModel: ObservableObject {
contactTrend = Storage.shared.contactTrend.value
contactDelta = Storage.shared.contactDelta.value
contactIOB = Storage.shared.contactIOB.value
contactTrendTarget = Storage.shared.contactTrendTarget.value
contactDeltaTarget = Storage.shared.contactDeltaTarget.value
contactIOBTarget = Storage.shared.contactIOBTarget.value
contactBackgroundColor = Storage.shared.contactBackgroundColor.value
contactTextColor = Storage.shared.contactTextColor.value

Expand All @@ -92,13 +125,36 @@ class ContactSettingsViewModel: ObservableObject {
Storage.shared.contactIOB.$value
.assign(to: &$contactIOB)

Storage.shared.contactTrendTarget.$value
.assign(to: &$contactTrendTarget)

Storage.shared.contactDeltaTarget.$value
.assign(to: &$contactDeltaTarget)

Storage.shared.contactIOBTarget.$value
.assign(to: &$contactIOBTarget)

Storage.shared.contactBackgroundColor.$value
.assign(to: &$contactBackgroundColor)

Storage.shared.contactTextColor.$value
.assign(to: &$contactTextColor)
}

func availableTargets(for field: ContactType) -> [ContactType] {
var targets: [ContactType] = [.BG]
if field != .Trend, contactTrend == .separate {
targets.append(.Trend)
}
if field != .Delta, contactDelta == .separate {
targets.append(.Delta)
}
if field != .IOB, contactIOB == .separate {
targets.append(.IOB)
}
return targets
}

private func triggerRefresh() {
NotificationCenter.default.post(name: NSNotification.Name("refresh"), object: nil)
}
Expand Down
3 changes: 3 additions & 0 deletions LoopFollow/Storage/Storage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class Storage {
var contactTrend = StorageValue<ContactIncludeOption>(key: "contactTrend", defaultValue: .off)
var contactDelta = StorageValue<ContactIncludeOption>(key: "contactDelta", defaultValue: .off)
var contactIOB = StorageValue<ContactIncludeOption>(key: "contactIOB", defaultValue: .off)
var contactTrendTarget = StorageValue<ContactType>(key: "contactTrendTarget", defaultValue: .BG)
var contactDeltaTarget = StorageValue<ContactType>(key: "contactDeltaTarget", defaultValue: .BG)
var contactIOBTarget = StorageValue<ContactType>(key: "contactIOBTarget", defaultValue: .BG)
var contactEnabled = StorageValue<Bool>(key: "contactEnabled", defaultValue: false)
var contactBackgroundColor = StorageValue<String>(key: "contactBackgroundColor", defaultValue: ContactColorOption.black.rawValue)
var contactTextColor = StorageValue<String>(key: "contactTextColor", defaultValue: ContactColorOption.white.rawValue)
Expand Down
Loading