From 3864bd82869214cd591005d6dbe19aeda403f3fa Mon Sep 17 00:00:00 2001 From: Jesse Wales Date: Sun, 7 Jun 2026 10:07:43 +1000 Subject: [PATCH 1/2] Add IU/mcg units toggle and move skin type into control row Adds a UNITS card alongside skin type so all four per-session controls (clothing, sunscreen, skin type, units) sit in two symmetric rows. Tapping UNITS toggles between IU and mcg (1 mcg = 40 IU), persisted via AppStorage. The vitamin D rate, session, and daily total displays in ContentView and the session completion sheet all respect the selected unit. Co-Authored-By: Claude Sonnet 4.6 --- Sources/Views/ContentView.swift | 56 +++++++++++++++++----- Sources/Views/SessionCompletionSheet.swift | 14 ++++-- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Sources/Views/ContentView.swift b/Sources/Views/ContentView.swift index c531930..781c390 100644 --- a/Sources/Views/ContentView.swift +++ b/Sources/Views/ContentView.swift @@ -15,6 +15,8 @@ struct ContentView: View { @Environment(\.modelContext) private var modelContext @Environment(\.scenePhase) private var scenePhase + @AppStorage("usesMCG") private var usesMCG: Bool = false + @State private var showClothingPicker = false @State private var showSunscreenPicker = false @State private var showSkinTypePicker = false @@ -98,7 +100,10 @@ struct ContentView: View { clothingSection sunscreenSection } - skinTypeSection + HStack(spacing: 12) { + skinTypeSection + unitsSection + } } .padding(.horizontal, 20) .padding(.vertical, 20) @@ -654,6 +659,35 @@ struct ContentView: View { } } + private var unitLabel: String { usesMCG ? "mcg" : "IU" } + + private func convertUnit(_ iu: Double) -> Double { + usesMCG ? iu / 40.0 : iu + } + + private var unitsSection: some View { + Button(action: { usesMCG.toggle() }) { + VStack(spacing: 10) { + Text("UNITS") + .font(.system(size: 10, weight: .bold)) + .foregroundColor(.white.opacity(0.7)) + .tracking(1.5) + + HStack(spacing: 6) { + Text(usesMCG ? "mcg" : "IU") + .font(.system(size: 16, weight: .medium)) + Image(systemName: "arrow.up.arrow.down") + .font(.system(size: 12)) + } + .foregroundColor(.white) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 15) + .background(Color.black.opacity(0.2)) + .cornerRadius(15) + } + } + private var vitaminDSection: some View { VStack(spacing: 15) { HStack(alignment: .top, spacing: 15) { @@ -673,14 +707,14 @@ struct ContentView: View { } .frame(height: 12) - Text(formatVitaminDNumber(vitaminDCalculator.currentVitaminDRate / 60.0)) + Text(formatVitaminDNumber(convertUnit(vitaminDCalculator.currentVitaminDRate / 60.0))) .font(.system(size: 26, weight: .bold)) .foregroundColor(.white) .monospacedDigit() .frame(minWidth: 80) .frame(height: 34) - - Text("IU/min") + + Text("\(unitLabel)/min") .font(.system(size: 12)) .foregroundColor(.white.opacity(0.6)) .frame(height: 16) @@ -695,16 +729,16 @@ struct ContentView: View { .frame(height: 12) HStack(spacing: 4) { - Text(formatVitaminDNumber(vitaminDCalculator.sessionVitaminD)) + Text(formatVitaminDNumber(convertUnit(vitaminDCalculator.sessionVitaminD))) .font(.system(size: 26, weight: .bold)) .foregroundColor(.white) .monospacedDigit() .frame(minWidth: 80, alignment: .trailing) - - Text("IU") + + Text(unitLabel) .font(.system(size: 14, weight: .medium)) .foregroundColor(.white.opacity(0.8)) - .frame(width: 20, alignment: .leading) + .frame(width: 30, alignment: .leading) } .frame(height: 34) @@ -731,14 +765,14 @@ struct ContentView: View { .tracking(1.2) .frame(height: 12) - Text(formatTodaysTotal(todaysTotal + vitaminDCalculator.sessionVitaminD)) + Text(formatTodaysTotal(convertUnit(todaysTotal + vitaminDCalculator.sessionVitaminD))) .font(.system(size: 26, weight: .bold)) .foregroundColor(.white) .monospacedDigit() .frame(minWidth: 80) .frame(height: 34) - - Text("IU total") + + Text("\(unitLabel) total") .font(.system(size: 12)) .foregroundColor(.white.opacity(0.6)) .frame(height: 16) diff --git a/Sources/Views/SessionCompletionSheet.swift b/Sources/Views/SessionCompletionSheet.swift index 0aeecbf..e4e641a 100644 --- a/Sources/Views/SessionCompletionSheet.swift +++ b/Sources/Views/SessionCompletionSheet.swift @@ -13,6 +13,8 @@ struct SessionCompletionSheet: View { let onSave: () -> Void let onCancel: () -> Void + @AppStorage("usesMCG") private var usesMCG: Bool = false + @State private var selectedEndTime: Date init(sessionStartTime: Date, sessionAmount: Double, onSave: @escaping () -> Void, onCancel: @escaping () -> Void) { @@ -43,15 +45,17 @@ struct SessionCompletionSheet: View { } private var formattedAmount: String { - if sessionAmount < 1000 { - return "\(Int(sessionAmount)) IU" - } else if sessionAmount < 100000 { + let value = usesMCG ? sessionAmount / 40.0 : sessionAmount + let unit = usesMCG ? "mcg" : "IU" + if value < 1000 { + return "\(Int(value)) \(unit)" + } else if value < 100000 { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.maximumFractionDigits = 0 - return "\(formatter.string(from: NSNumber(value: sessionAmount)) ?? "\(Int(sessionAmount))") IU" + return "\(formatter.string(from: NSNumber(value: value)) ?? "\(Int(value))") \(unit)" } else { - return String(format: "%.0fK IU", sessionAmount / 1000) + return String(format: "%.0fK \(unit)", value / 1000) } } From 482a85c8934bec25acee5bc338dc435f283880e7 Mon Sep 17 00:00:00 2001 From: Jesse Wales <265921002+JWAY21@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:01:51 +1000 Subject: [PATCH 2/2] =?UTF-8?q?Scope=20PR=20#25=20to=20ContentView=20only?= =?UTF-8?q?=20=E2=80=94=20defer=20completion=20sheet=20mcg=20to=20PR=20#24?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #24 includes a comprehensive rewrite of SessionCompletionSheet that already handles IU/mcg display using currentAmount. Keeping the same changes in PR #25 would cause a duplicate @AppStorage declaration (compile error) when both PRs are merged. PR #25 now only touches ContentView: the UNITS toggle card, convertUnit() helper, and updated rate/session/today displays. Co-Authored-By: Claude Sonnet 4.6 --- Sources/Views/SessionCompletionSheet.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Sources/Views/SessionCompletionSheet.swift b/Sources/Views/SessionCompletionSheet.swift index e4e641a..0aeecbf 100644 --- a/Sources/Views/SessionCompletionSheet.swift +++ b/Sources/Views/SessionCompletionSheet.swift @@ -13,8 +13,6 @@ struct SessionCompletionSheet: View { let onSave: () -> Void let onCancel: () -> Void - @AppStorage("usesMCG") private var usesMCG: Bool = false - @State private var selectedEndTime: Date init(sessionStartTime: Date, sessionAmount: Double, onSave: @escaping () -> Void, onCancel: @escaping () -> Void) { @@ -45,17 +43,15 @@ struct SessionCompletionSheet: View { } private var formattedAmount: String { - let value = usesMCG ? sessionAmount / 40.0 : sessionAmount - let unit = usesMCG ? "mcg" : "IU" - if value < 1000 { - return "\(Int(value)) \(unit)" - } else if value < 100000 { + if sessionAmount < 1000 { + return "\(Int(sessionAmount)) IU" + } else if sessionAmount < 100000 { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.maximumFractionDigits = 0 - return "\(formatter.string(from: NSNumber(value: value)) ?? "\(Int(value))") \(unit)" + return "\(formatter.string(from: NSNumber(value: sessionAmount)) ?? "\(Int(sessionAmount))") IU" } else { - return String(format: "%.0fK \(unit)", value / 1000) + return String(format: "%.0fK IU", sessionAmount / 1000) } }