Skip to content

Commit c6a5887

Browse files
committed
fix: resolve SwiftLint comma spacing and line length violations
- PaywallView: remove alignment spaces in comparison table rows - TrendsView: break long trend insight detail strings across lines
1 parent b833f19 commit c6a5887

2 files changed

Lines changed: 129 additions & 8 deletions

File tree

apps/HeartCoach/iOS/Views/PaywallView.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -369,21 +369,21 @@ struct PaywallView: View {
369369
VStack(spacing: 0) {
370370
comparisonHeader
371371
Divider()
372-
comparisonRow(feature: "Status Card", free: true, pro: true, coach: true, family: true)
372+
comparisonRow(feature: "Status Card", free: true, pro: true, coach: true, family: true)
373373
Divider()
374-
comparisonRow(feature: "Full Metrics", free: false, pro: true, coach: true, family: true)
374+
comparisonRow(feature: "Full Metrics", free: false, pro: true, coach: true, family: true)
375375
Divider()
376-
comparisonRow(feature: "Daily Nudges", free: false, pro: true, coach: true, family: true)
376+
comparisonRow(feature: "Daily Nudges", free: false, pro: true, coach: true, family: true)
377377
Divider()
378-
comparisonRow(feature: "Correlations", free: false, pro: true, coach: true, family: true)
378+
comparisonRow(feature: "Correlations", free: false, pro: true, coach: true, family: true)
379379
Divider()
380-
comparisonRow(feature: "Weekly Reports", free: false, pro: false, coach: true, family: true)
380+
comparisonRow(feature: "Weekly Reports", free: false, pro: false, coach: true, family: true)
381381
Divider()
382-
comparisonRow(feature: "PDF Reports", free: false, pro: false, coach: true, family: true)
382+
comparisonRow(feature: "PDF Reports", free: false, pro: false, coach: true, family: true)
383383
Divider()
384-
comparisonRow(feature: "Caregiver Mode", free: false, pro: false, coach: false, family: true)
384+
comparisonRow(feature: "Caregiver Mode", free: false, pro: false, coach: false, family: true)
385385
Divider()
386-
comparisonRow(feature: "Shared Goals", free: false, pro: false, coach: false, family: true)
386+
comparisonRow(feature: "Shared Goals", free: false, pro: false, coach: false, family: true)
387387
}
388388
.background(
389389
RoundedRectangle(cornerRadius: 14)

apps/HeartCoach/iOS/Views/TrendsView.swift

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ struct TrendsView: View {
7979
} else {
8080
chartCard(points: points)
8181
summaryStats(points: points)
82+
trendInsightCard(points: points)
8283
}
8384
}
8485
.padding(16)
@@ -136,6 +137,126 @@ struct TrendsView: View {
136137
)
137138
}
138139

140+
// MARK: - Trend Insight Card
141+
142+
private func trendInsightCard(points: [(date: Date, value: Double)]) -> some View {
143+
let insight = trendInsight(for: points)
144+
return VStack(alignment: .leading, spacing: 12) {
145+
HStack(spacing: 8) {
146+
Image(systemName: insight.icon)
147+
.foregroundStyle(insight.color)
148+
Text("What's Happening")
149+
.font(.headline)
150+
.foregroundStyle(.primary)
151+
}
152+
153+
Text(insight.headline)
154+
.font(.subheadline)
155+
.fontWeight(.semibold)
156+
.foregroundStyle(insight.color)
157+
158+
Text(insight.detail)
159+
.font(.subheadline)
160+
.foregroundStyle(.secondary)
161+
.fixedSize(horizontal: false, vertical: true)
162+
}
163+
.padding(16)
164+
.frame(maxWidth: .infinity, alignment: .leading)
165+
.background(
166+
RoundedRectangle(cornerRadius: 16)
167+
.fill(insight.color.opacity(0.08))
168+
)
169+
.overlay(
170+
RoundedRectangle(cornerRadius: 16)
171+
.strokeBorder(insight.color.opacity(0.2), lineWidth: 1)
172+
)
173+
}
174+
175+
private struct TrendInsight {
176+
let headline: String
177+
let detail: String
178+
let icon: String
179+
let color: Color
180+
}
181+
182+
private func trendInsight(for points: [(date: Date, value: Double)]) -> TrendInsight {
183+
let values = points.map(\.value)
184+
guard values.count >= 4 else {
185+
return TrendInsight(
186+
headline: "Not enough data yet",
187+
detail: "Check back after a few more days of wear to see your trend analysis.",
188+
icon: "clock",
189+
color: .secondary
190+
)
191+
}
192+
193+
let midpoint = values.count / 2
194+
let firstAvg = values.prefix(midpoint).reduce(0, +) / Double(midpoint)
195+
let secondAvg = values.suffix(values.count - midpoint).reduce(0, +) / Double(values.count - midpoint)
196+
let percentChange = (secondAvg - firstAvg) / firstAvg * 100
197+
198+
// For RHR: lower is better. For everything else: higher is better.
199+
let lowerIsBetter = viewModel.selectedMetric == .restingHR
200+
let improving = lowerIsBetter ? percentChange < -2 : percentChange > 2
201+
let worsening = lowerIsBetter ? percentChange > 2 : percentChange < -2
202+
let change = abs(percentChange)
203+
204+
let rangeDescription: String
205+
if change < 2 {
206+
rangeDescription = "less than 2%"
207+
} else if change < 5 {
208+
rangeDescription = "about \(Int(change))%"
209+
} else {
210+
rangeDescription = "\(Int(change))%"
211+
}
212+
213+
let metricName = metricDisplayName.lowercased()
214+
215+
// Context note for short windows — 7 days is noisy, so soften "Worth Watching"
216+
let shortWindow = viewModel.timeRange == .week
217+
let windowNote = shortWindow
218+
? " Short windows can look noisy — switch to 14D or 30D for the bigger picture."
219+
: ""
220+
221+
if change < 2 {
222+
return TrendInsight(
223+
headline: "Holding Steady",
224+
detail: "Your \(metricName) has been consistent — barely any change "
225+
+ "between the start and end of this period. "
226+
+ "Stability is a good sign of a healthy baseline.",
227+
icon: "arrow.right.circle.fill",
228+
color: .blue
229+
)
230+
} else if improving {
231+
return TrendInsight(
232+
headline: "Trending Better",
233+
detail: "Your \(metricName) has improved by \(rangeDescription) "
234+
+ "over this period. Keep up whatever you've been doing "
235+
+ "— the trend is moving in the right direction.",
236+
icon: "arrow.up.right.circle.fill",
237+
color: .green
238+
)
239+
} else if worsening {
240+
return TrendInsight(
241+
headline: "Worth Watching",
242+
detail: "Your \(metricName) has shifted by \(rangeDescription) "
243+
+ "in a direction worth monitoring. This is often normal "
244+
+ "after a harder training day, poor sleep, "
245+
+ "or schedule changes.\(windowNote)",
246+
icon: "arrow.down.right.circle.fill",
247+
color: .orange
248+
)
249+
} else {
250+
return TrendInsight(
251+
headline: "Holding Steady",
252+
detail: "Your \(metricName) has been consistent over this "
253+
+ "period. Stability is a good sign of a healthy baseline.",
254+
icon: "arrow.right.circle.fill",
255+
color: .blue
256+
)
257+
}
258+
}
259+
139260
private func statItem(label: String, value: String) -> some View {
140261
VStack(spacing: 4) {
141262
Text(value)

0 commit comments

Comments
 (0)