Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ extension DynamicSettings {
}

@Published var hasValidTDD: Bool = false
@Published var currentDataPoints: Int = 0
@Published var requiredDataPoints: Int = Int(Double(7 * 288) * 0.85) // ~1714
@Published var useNewFormula: Bool = false
@Published var sigmoid: Bool = false
@Published var adjustmentFactor: Decimal = 0.8
Expand Down Expand Up @@ -61,13 +63,16 @@ extension DynamicSettings {
Task {
do {
let hasValidTDD = try await tddStorage.hasSufficientTDD()
let dataCount = try await getTDDDataCount()
await MainActor.run {
self.hasValidTDD = hasValidTDD
self.currentDataPoints = dataCount
}
} catch {
debug(.coreData, "Error when fetching TDD for validity checking: \(error)")
await MainActor.run {
hasValidTDD = false
currentDataPoints = 0
}
}
}
Expand Down Expand Up @@ -119,6 +124,22 @@ extension DynamicSettings {

return result
}

// Gets the count of TDD data points in the last 7 days
// - Returns: The number of TDD records
// - Throws: An error if the Core Data count operation fails
private func getTDDDataCount() async throws -> Int {
try await context.perform {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "TDDStored")
fetchRequest.predicate = NSPredicate(
format: "date > %@ AND total > 0",
Date().addingTimeInterval(-86400 * 7) as NSDate
)
fetchRequest.resultType = .countResultType

return try self.context.count(for: fetchRequest)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ extension DynamicSettings {

var body: some View {
List {
// Show progress view if Dynamic ISF is not yet available
if !state.hasValidTDD {
Section {
DynamicISFProgressView(
currentDataPoints: state.currentDataPoints,
requiredDataPoints: state.requiredDataPoints
)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.listRowBackground(Color.clear)
}
}

Section(
header: Text("Dynamic Insulin Sensitivity"),
content: {
Expand All @@ -62,7 +74,7 @@ extension DynamicSettings {
localized: "Dynamically adjust insulin sensitivity using Dynamic Ratio rather than Autosens Ratio."
) :
String(
localized: "Trio has only been actively used and looping for less than seven days. Cannot enable dynamic ISF."
localized: "Dynamic ISF requires 7 days of continuous data."
)
let miniHintTextColorForDisabled: Color = colorScheme == .dark ? .orange :
.accentColor
Expand Down
122 changes: 122 additions & 0 deletions Trio/Sources/Views/DynamicISFProgressView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import SwiftUI

struct DynamicISFProgressView: View {
let currentDataPoints: Int
let requiredDataPoints: Int

private var progress: Double {
guard requiredDataPoints > 0 else { return 0 }
return min(Double(currentDataPoints) / Double(requiredDataPoints), 1.0)
}

private var progressPercentage: Int {
Int(progress * 100)
}

private var daysOfData: Double {
// Calculate days based on data points (288 per day)
Double(currentDataPoints) / 288.0
}

private var formattedDays: String {
if daysOfData < 1 {
let hours = Int(daysOfData * 24)
return String(localized: "\(hours) hours")
} else {
return String(format: "%.1f days", daysOfData)
}
}

var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "chart.line.uptrend.xyaxis")
.foregroundColor(.orange)
Text("Dynamic ISF Warmup")
.font(.headline)
.foregroundColor(.primary)
Spacer()
}

VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Accumulating Data...")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
Text("\(progressPercentage)%")
.font(.caption)
.foregroundColor(.secondary)
}

GeometryReader { geometry in
ZStack(alignment: .leading) {
// Background
RoundedRectangle(cornerRadius: 8)
.fill(Color(.systemGray5))
.frame(height: 8)

// Progress
RoundedRectangle(cornerRadius: 8)
.fill(
LinearGradient(
gradient: Gradient(colors: [Color.orange.opacity(0.8), Color.orange]),
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: geometry.size.width * progress, height: 8)
.animation(.easeInOut(duration: 0.3), value: progress)
}
}
.frame(height: 8)

HStack {
Text("\(formattedDays) of data collected")
.font(.caption2)
.foregroundColor(.secondary)
Spacer()
Text("7 days required")
.font(.caption2)
.foregroundColor(.secondary)
}
}

if progress >= 1.0 {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Dynamic ISF is ready to use!")
.font(.caption)
.foregroundColor(.green)
}
.padding(.top, 4)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(.secondarySystemBackground))
)
}
}

// Preview
struct DynamicISFProgressView_Previews: PreviewProvider {
static var previews: some View {
VStack(spacing: 20) {
// No data
DynamicISFProgressView(currentDataPoints: 0, requiredDataPoints: 1714)

// Partial data (3 days)
DynamicISFProgressView(currentDataPoints: 864, requiredDataPoints: 1714)

// Almost ready (6.5 days)
DynamicISFProgressView(currentDataPoints: 1872, requiredDataPoints: 1714)

// Ready
DynamicISFProgressView(currentDataPoints: 1714, requiredDataPoints: 1714)
}
.padding()
}
}