|
|
@@ -207,9 +207,6 @@ extension Home.RootView {
|
|
|
.foregroundStyle(.secondary)
|
|
|
}
|
|
|
}
|
|
|
- .onTapGesture {
|
|
|
- selectedTab = 2
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
@ViewBuilder func adjustmentsTempTargetView(_ tempTargetString: String) -> some View {
|
|
|
@@ -223,9 +220,6 @@ extension Home.RootView {
|
|
|
.foregroundStyle(.secondary)
|
|
|
}
|
|
|
}
|
|
|
- .onTapGesture {
|
|
|
- selectedTab = 2
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
@ViewBuilder func adjustmentsCancelView(_ cancelAction: @escaping () -> Void) -> some View {
|
|
|
@@ -305,8 +299,6 @@ extension Home.RootView {
|
|
|
Image(systemName: "xmark.app")
|
|
|
.font(.title)
|
|
|
.foregroundStyle(Color.clear)
|
|
|
- }.onTapGesture {
|
|
|
- selectedTab = 2
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -359,16 +351,19 @@ extension Home.RootView {
|
|
|
)
|
|
|
.frame(height: HomeLayout.bottomPanelHeight)
|
|
|
.overlay(alignment: .bottom) {
|
|
|
- if isConcurrent {
|
|
|
- HStack(spacing: 6) {
|
|
|
- remainingBar(overrideRemainingFraction, tint: .purple)
|
|
|
- remainingBar(tempTargetRemainingFraction, tint: .loopGreen)
|
|
|
+ // inset clears the corner curve so the bar stays inside the shape
|
|
|
+ Group {
|
|
|
+ if isConcurrent {
|
|
|
+ HStack(spacing: 6) {
|
|
|
+ remainingBar(overrideRemainingFraction, tint: .purple)
|
|
|
+ remainingBar(tempTargetRemainingFraction, tint: .loopGreen)
|
|
|
+ }
|
|
|
+ } else if let tint = tint {
|
|
|
+ remainingBar(overrideRemainingFraction ?? tempTargetRemainingFraction, tint: tint)
|
|
|
}
|
|
|
- .padding(.horizontal, 4)
|
|
|
- } else if let tint = tint {
|
|
|
- remainingBar(overrideRemainingFraction ?? tempTargetRemainingFraction, tint: tint)
|
|
|
- .padding(.horizontal, 4)
|
|
|
}
|
|
|
+ .padding(.horizontal, 14)
|
|
|
+ .padding(.bottom, 3)
|
|
|
}
|
|
|
.clipShape(RoundedRectangle(cornerRadius: 17, style: .continuous))
|
|
|
.shadow(color: Color.black.opacity(colorScheme == .dark ? 0.25 : 0.10), radius: 3, y: 1)
|
|
|
@@ -440,7 +435,13 @@ extension Home.RootView {
|
|
|
} message: {
|
|
|
Text("Select Adjustment")
|
|
|
}
|
|
|
- }.padding(.horizontal, 10)
|
|
|
+ }
|
|
|
+ // whole panel navigates; the cancel buttons' own gestures take precedence
|
|
|
+ .contentShape(Rectangle())
|
|
|
+ .onTapGesture {
|
|
|
+ selectedTab = 2
|
|
|
+ }
|
|
|
+ .padding(.horizontal, 10)
|
|
|
}
|
|
|
|
|
|
@ViewBuilder func bolusView(_ progress: Decimal) -> some View {
|
|
|
@@ -531,7 +532,37 @@ extension Home.RootView {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Mean glucose (mg/dL) of today's readings, nil without data.
|
|
|
+ private var todayMeanGlucose: Double? {
|
|
|
+ let startOfDay = Calendar.current.startOfDay(for: Date())
|
|
|
+ let values = state.glucoseFromPersistence
|
|
|
+ .filter { ($0.date ?? .distantPast) >= startOfDay }
|
|
|
+ .map { Double($0.glucose) }
|
|
|
+ guard !values.isEmpty else { return nil }
|
|
|
+ return values.reduce(0, +) / Double(values.count)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var todayAverageString: String {
|
|
|
+ guard let mean = todayMeanGlucose else { return "--" }
|
|
|
+ if state.units == .mmolL {
|
|
|
+ return Decimal(mean).asMmolL.formatted(.number.precision(.fractionLength(1))) + " " + GlucoseUnits.mmolL.rawValue
|
|
|
+ }
|
|
|
+ return "\(Int(mean.rounded())) " + GlucoseUnits.mgdL.rawValue
|
|
|
+ }
|
|
|
+
|
|
|
+ private var todayGMIString: String {
|
|
|
+ guard let mean = todayMeanGlucose else { return "--" }
|
|
|
+ let gmiPercentage = 3.31 + 0.02392 * mean
|
|
|
+ // settingsManager is injected after first render; default until then
|
|
|
+ if state.settingsManager?.settings.eA1cDisplayUnit == .mmolMol {
|
|
|
+ let gmiMmolMol = (gmiPercentage - 2.152) * 10.929
|
|
|
+ return "\(Int(gmiMmolMol.rounded())) mmol/mol"
|
|
|
+ }
|
|
|
+ return gmiPercentage.formatted(.number.precision(.fractionLength(1))) + " %"
|
|
|
+ }
|
|
|
+
|
|
|
@ViewBuilder func statsBanner() -> some View {
|
|
|
+ let face = state.settingsManager?.settings.homeStatsPanelFace ?? .timeInRange
|
|
|
let distribution = state.todayGlucoseDistribution
|
|
|
let coveragePct = distribution.veryLowPct + distribution.lowPct + distribution.inRangePct + distribution
|
|
|
.highPct + distribution.veryHighPct
|
|
|
@@ -565,18 +596,39 @@ extension Home.RootView {
|
|
|
.shadow(color: Color.black.opacity(colorScheme == .dark ? 0.25 : 0.10), radius: 3, y: 1)
|
|
|
|
|
|
HStack(alignment: .center, spacing: 12) {
|
|
|
- VStack(alignment: .leading, spacing: 4) {
|
|
|
- HStack(alignment: .firstTextBaseline, spacing: 6) {
|
|
|
- Text(tirString)
|
|
|
- .font(.title2).fontWeight(.bold).fontDesign(.rounded)
|
|
|
- .foregroundStyle(.primary)
|
|
|
+ switch face {
|
|
|
+ case .timeInRange:
|
|
|
+ VStack(alignment: .leading, spacing: 4) {
|
|
|
+ HStack(alignment: .firstTextBaseline, spacing: 6) {
|
|
|
+ Text(tirString)
|
|
|
+ .font(.title2).fontWeight(.bold).fontDesign(.rounded)
|
|
|
+ .foregroundStyle(.primary)
|
|
|
+ Text("Time in Range", comment: "Stats banner subtitle")
|
|
|
+ .font(.subheadline)
|
|
|
+ .foregroundStyle(.secondary)
|
|
|
+ }
|
|
|
+
|
|
|
+ statsDistributionBar(segments)
|
|
|
+ .frame(height: 6)
|
|
|
+ }
|
|
|
+ case .distributionBar:
|
|
|
+ VStack(alignment: .leading, spacing: 6) {
|
|
|
Text("Time in Range", comment: "Stats banner subtitle")
|
|
|
- .font(.subheadline)
|
|
|
+ .font(.subheadline).fontWeight(.semibold)
|
|
|
.foregroundStyle(.secondary)
|
|
|
- }
|
|
|
|
|
|
- statsDistributionBar(segments)
|
|
|
- .frame(height: 6)
|
|
|
+ statsDistributionBar(segments)
|
|
|
+ .frame(height: 6)
|
|
|
+ }
|
|
|
+ case .averages:
|
|
|
+ VStack(alignment: .leading, spacing: 1) {
|
|
|
+ Text("\u{2300} \(todayAverageString) \u{00B7} GMI \(todayGMIString)")
|
|
|
+ .font(.subheadline).fontWeight(.semibold)
|
|
|
+ .foregroundStyle(.primary)
|
|
|
+ Text("Today's average", comment: "Stats banner subtitle")
|
|
|
+ .font(.caption)
|
|
|
+ .foregroundStyle(.secondary)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
Spacer(minLength: 8)
|
|
|
@@ -588,6 +640,7 @@ extension Home.RootView {
|
|
|
.padding(.horizontal, 16)
|
|
|
}
|
|
|
.padding(.horizontal, 10)
|
|
|
+ .contentShape(Rectangle())
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
}
|