Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
1d9100c018
commit
a1d3105261
@ -3,11 +3,27 @@
|
|||||||
"strings" : {
|
"strings" : {
|
||||||
"" : {
|
"" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
" : " : {
|
||||||
|
"comment" : "A separator between the time of day and the time range in the Ritual Detail View.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"-%lld%% vs last week" : {
|
"-%lld%% vs last week" : {
|
||||||
"comment" : "A description of how a user's usage has changed compared to the previous week. The argument is the percentage by which the usage has increased or decreased.",
|
"comment" : "A description of how a user's usage has changed compared to the previous week. The argument is the percentage by which the usage has increased or decreased.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"%@ – %@" : {
|
||||||
|
"comment" : "A subline of text showing the start and end dates of an arc.",
|
||||||
|
"isCommentAutoGenerated" : true,
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "%1$@ – %2$@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"%@, Day %lld" : {
|
"%@, Day %lld" : {
|
||||||
"comment" : "A view representing a milestone achievement in a ritual journey. The first argument is the title of the milestone. The second argument is the day on which the milestone was achieved.",
|
"comment" : "A view representing a milestone achievement in a ritual journey. The first argument is the title of the milestone. The second argument is the day on which the milestone was achieved.",
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
@ -24,6 +40,18 @@
|
|||||||
"comment" : "A text view displaying the day number in a history calendar cell. The text is centered and has a small font size.",
|
"comment" : "A text view displaying the day number in a history calendar cell. The text is centered and has a small font size.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"%lld arc%@" : {
|
||||||
|
"comment" : "A badge displaying the number of completed arcs for a ritual. The argument is the count of completed arcs.",
|
||||||
|
"isCommentAutoGenerated" : true,
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "%1$lld arc%2$@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"%lld days" : {
|
"%lld days" : {
|
||||||
"comment" : "A label displaying the duration of a preset in days. The argument is the number of days the preset is active.",
|
"comment" : "A label displaying the duration of a preset in days. The argument is the number of days the preset is active.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -126,6 +154,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"%lld%% complete" : {
|
||||||
|
"comment" : "A percentage indicating the completion status of a ritual. The value is an integer percentage.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"%lld%% completion over %lld days" : {
|
"%lld%% completion over %lld days" : {
|
||||||
"comment" : "A string summarizing the completion rate of a ritual arc. The first argument is the completion rate, expressed as a percentage. The second argument is the duration of the arc in days.",
|
"comment" : "A string summarizing the completion rate of a ritual arc. The first argument is the completion rate, expressed as a percentage. The second argument is the duration of the arc in days.",
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
@ -701,6 +733,10 @@
|
|||||||
"comment" : "Label for the x-axis in the mini sparkline chart within an `InsightCardView`.",
|
"comment" : "Label for the x-axis in the mini sparkline chart within an `InsightCardView`.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Day %lld" : {
|
||||||
|
"comment" : "A sublabel showing the day and title of a milestone achievement. The first argument is the day of the milestone. The second argument is the title of the milestone.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Day %lld of %lld" : {
|
"Day %lld of %lld" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
|
|||||||
@ -21,9 +21,9 @@ struct EmptyStateCardView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
||||||
StyledLabel(title, .title2Bold, emphasis: .primary)
|
Text(title).styled(.title2Bold, emphasis: .primary)
|
||||||
|
|
||||||
StyledLabel(message, .body, emphasis: .secondary)
|
Text(message).styled(.body, emphasis: .secondary)
|
||||||
|
|
||||||
Button(action: action) {
|
Button(action: action) {
|
||||||
// Needs .frame() modifiers
|
// Needs .frame() modifiers
|
||||||
|
|||||||
@ -12,9 +12,9 @@ struct SectionHeaderView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
|
||||||
StyledLabel(title, .heading, emphasis: .primary)
|
Text(title).styled(.heading, emphasis: .primary)
|
||||||
if let subtitle {
|
if let subtitle {
|
||||||
StyledLabel(subtitle, .subheading, emphasis: .secondary)
|
Text(subtitle).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ struct InsightCardView: View {
|
|||||||
private var cardContent: some View {
|
private var cardContent: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
||||||
HStack {
|
HStack {
|
||||||
StyledLabel(card.title, .subheadingEmphasis, emphasis: .secondary)
|
Text(card.title).styled(.subheadingEmphasis, emphasis: .secondary)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
@ -32,11 +32,11 @@ struct InsightCardView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show value prominently
|
// Show value prominently
|
||||||
StyledLabel(card.value, .titleBold, emphasis: .primary)
|
Text(card.value).styled(.titleBold, emphasis: .primary)
|
||||||
|
|
||||||
// Show caption if present (non-chart cards)
|
// Show caption if present (non-chart cards)
|
||||||
if !card.caption.isEmpty {
|
if !card.caption.isEmpty {
|
||||||
StyledLabel(card.caption, .caption, emphasis: .secondary)
|
Text(card.caption).styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show full-width mini bar chart at bottom (Athlytic style)
|
// Show full-width mini bar chart at bottom (Athlytic style)
|
||||||
|
|||||||
@ -101,7 +101,8 @@ struct InsightDetailSheet: View {
|
|||||||
private var trendIndicatorSection: some View {
|
private var trendIndicatorSection: some View {
|
||||||
HStack(spacing: Design.Spacing.medium) {
|
HStack(spacing: Design.Spacing.medium) {
|
||||||
// Trend direction badge
|
// Trend direction badge
|
||||||
IconLabel(trendDirection.symbolName, trendDirection.accessibilityLabel, .caption, emphasis: .custom(trendDirection.color))
|
Label(trendDirection.accessibilityLabel, systemImage: trendDirection.symbolName)
|
||||||
|
.styled(.caption, emphasis: .custom(trendDirection.color))
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
.background(trendDirection.color.opacity(Design.Opacity.subtle))
|
.background(trendDirection.color.opacity(Design.Opacity.subtle))
|
||||||
@ -114,7 +115,7 @@ struct InsightDetailSheet: View {
|
|||||||
? String(localized: "+\(changePercent)% vs last week")
|
? String(localized: "+\(changePercent)% vs last week")
|
||||||
: String(localized: "-\(changePercent)% vs last week")
|
: String(localized: "-\(changePercent)% vs last week")
|
||||||
|
|
||||||
StyledLabel(changeText, .caption, emphasis: .secondary)
|
Text(changeText).styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -126,7 +127,8 @@ struct InsightDetailSheet: View {
|
|||||||
|
|
||||||
private var tipsSection: some View {
|
private var tipsSection: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
IconLabel("lightbulb.fill", String(localized: "Tips"), .heading, emphasis: .primary)
|
Label(String(localized: "Tips"), systemImage: "lightbulb.fill")
|
||||||
|
.styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
ForEach(tips, id: \.self) { tip in
|
ForEach(tips, id: \.self) { tip in
|
||||||
@ -134,7 +136,7 @@ struct InsightDetailSheet: View {
|
|||||||
SymbolIcon("chevron.right", size: .badge, color: AppAccent.primary)
|
SymbolIcon("chevron.right", size: .badge, color: AppAccent.primary)
|
||||||
.padding(.top, Design.Spacing.xSmall)
|
.padding(.top, Design.Spacing.xSmall)
|
||||||
|
|
||||||
StyledLabel(tip, .subheading, emphasis: .secondary)
|
Text(tip).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,9 +151,9 @@ struct InsightDetailSheet: View {
|
|||||||
|
|
||||||
private var explanationSection: some View {
|
private var explanationSection: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(String(localized: "What this means"), .heading, emphasis: .primary)
|
Text(String(localized: "What this means")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
StyledLabel(card.explanation, .body, emphasis: .secondary)
|
Text(card.explanation).styled(.body, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
@ -163,7 +165,7 @@ struct InsightDetailSheet: View {
|
|||||||
|
|
||||||
private func chartSection(_ data: [TrendDataPoint]) -> some View {
|
private func chartSection(_ data: [TrendDataPoint]) -> some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(String(localized: "7-Day Trend"), .heading, emphasis: .primary)
|
Text(String(localized: "7-Day Trend")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
Chart(data) { point in
|
Chart(data) { point in
|
||||||
BarMark(
|
BarMark(
|
||||||
@ -182,10 +184,8 @@ struct InsightDetailSheet: View {
|
|||||||
AxisMarks(values: [0, 0.5, 1.0]) { value in
|
AxisMarks(values: [0, 0.5, 1.0]) { value in
|
||||||
AxisValueLabel {
|
AxisValueLabel {
|
||||||
if let v = value.as(Double.self) {
|
if let v = value.as(Double.self) {
|
||||||
// Exception: Inside AxisValueLabel closure
|
|
||||||
Text("\(Int(v * 100))%")
|
Text("\(Int(v * 100))%")
|
||||||
.font(Typography.caption2.font)
|
.styled(.caption2, emphasis: .tertiary)
|
||||||
.foregroundStyle(AppTextColors.tertiary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AxisGridLine()
|
AxisGridLine()
|
||||||
@ -194,10 +194,10 @@ struct InsightDetailSheet: View {
|
|||||||
}
|
}
|
||||||
.chartXAxis {
|
.chartXAxis {
|
||||||
AxisMarks { _ in
|
AxisMarks { _ in
|
||||||
// Exception: AxisValueLabel doesn't support StyledLabel
|
// Exception: AxisValueLabel() doesn't support .styled()
|
||||||
AxisValueLabel()
|
AxisValueLabel()
|
||||||
.font(Typography.caption2.font)
|
.font(Typography.caption2.font)
|
||||||
.foregroundStyle(AppTextColors.secondary)
|
.foregroundStyle(Theme.Text.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 180)
|
.frame(height: 180)
|
||||||
@ -214,16 +214,16 @@ struct InsightDetailSheet: View {
|
|||||||
|
|
||||||
private func breakdownSection(_ breakdown: [BreakdownItem]) -> some View {
|
private func breakdownSection(_ breakdown: [BreakdownItem]) -> some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(String(localized: "Breakdown"), .heading, emphasis: .primary)
|
Text(String(localized: "Breakdown")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
VStack(spacing: Design.Spacing.xSmall) {
|
VStack(spacing: Design.Spacing.xSmall) {
|
||||||
ForEach(breakdown) { item in
|
ForEach(breakdown) { item in
|
||||||
HStack {
|
HStack {
|
||||||
StyledLabel(item.label, .subheading, emphasis: .primary)
|
Text(item.label).styled(.subheading, emphasis: .primary)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
StyledLabel(item.value, .subheading, emphasis: .secondary)
|
Text(item.value).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,9 @@ struct FirstCheckInStepView: View {
|
|||||||
private var tutorialView: some View {
|
private var tutorialView: some View {
|
||||||
VStack(spacing: Design.Spacing.xxLarge) {
|
VStack(spacing: Design.Spacing.xxLarge) {
|
||||||
// Header
|
// Header
|
||||||
StyledLabel(String(localized: "Let's try it out!"), .title2Bold, emphasis: .primary, alignment: .center)
|
Text(String(localized: "Let's try it out!"))
|
||||||
|
.styled(.title2Bold, emphasis: .primary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
.opacity(animateContent ? 1 : 0)
|
.opacity(animateContent ? 1 : 0)
|
||||||
|
|
||||||
// Ritual card with habits
|
// Ritual card with habits
|
||||||
@ -65,9 +67,9 @@ struct FirstCheckInStepView: View {
|
|||||||
SymbolIcon(ritual.iconName, size: .row, color: AppAccent.primary)
|
SymbolIcon(ritual.iconName, size: .row, color: AppAccent.primary)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
StyledLabel(String(localized: "Day 1 of \(ritual.durationDays)"), .caption, emphasis: .secondary)
|
Text(String(localized: "Day 1 of \(ritual.durationDays)")).styled(.caption, emphasis: .secondary)
|
||||||
|
|
||||||
StyledLabel(ritual.title, .heading, emphasis: .primary)
|
Text(ritual.title).styled(.heading, emphasis: .primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -93,14 +95,14 @@ struct FirstCheckInStepView: View {
|
|||||||
.offset(y: animateContent ? 0 : 20)
|
.offset(y: animateContent ? 0 : 20)
|
||||||
|
|
||||||
// Instruction
|
// Instruction
|
||||||
StyledLabel(String(localized: "Tap a habit to check in"), .subheading, emphasis: .secondary)
|
Text(String(localized: "Tap a habit to check in")).styled(.subheading, emphasis: .secondary)
|
||||||
.opacity(animateContent ? 1 : 0)
|
.opacity(animateContent ? 1 : 0)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(spacing: Design.Spacing.medium) {
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
Button(action: onComplete) {
|
Button(action: onComplete) {
|
||||||
StyledLabel(String(localized: "Continue"), .heading, emphasis: .inverse)
|
Text(String(localized: "Continue")).styled(.heading, emphasis: .inverse)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(height: AppMetrics.Size.buttonHeight)
|
.frame(height: AppMetrics.Size.buttonHeight)
|
||||||
.background(AppAccent.primary)
|
.background(AppAccent.primary)
|
||||||
@ -108,7 +110,7 @@ struct FirstCheckInStepView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button(action: onComplete) {
|
Button(action: onComplete) {
|
||||||
StyledLabel(String(localized: "Skip for now"), .subheading, emphasis: .secondary)
|
Text(String(localized: "Skip for now")).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, Design.Spacing.xxLarge)
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||||
@ -135,9 +137,11 @@ struct FirstCheckInStepView: View {
|
|||||||
.opacity(showCelebration ? 1 : 0)
|
.opacity(showCelebration ? 1 : 0)
|
||||||
|
|
||||||
VStack(spacing: Design.Spacing.medium) {
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
StyledLabel(String(localized: "Nice work!"), .heroBold, emphasis: .primary)
|
Text(String(localized: "Nice work!")).styled(.heroBold, emphasis: .primary)
|
||||||
|
|
||||||
StyledLabel(String(localized: "You completed your first check-in"), .title3, emphasis: .secondary, alignment: .center)
|
Text(String(localized: "You completed your first check-in"))
|
||||||
|
.styled(.title3, emphasis: .secondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -145,7 +149,7 @@ struct FirstCheckInStepView: View {
|
|||||||
// Continue button
|
// Continue button
|
||||||
if showContinueButton {
|
if showContinueButton {
|
||||||
Button(action: onComplete) {
|
Button(action: onComplete) {
|
||||||
StyledLabel(String(localized: "Continue to Rituals"), .heading, emphasis: .inverse)
|
Text(String(localized: "Continue to Rituals")).styled(.heading, emphasis: .inverse)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(height: AppMetrics.Size.buttonHeight)
|
.frame(height: AppMetrics.Size.buttonHeight)
|
||||||
.background(AppAccent.primary)
|
.background(AppAccent.primary)
|
||||||
@ -194,7 +198,7 @@ private struct OnboardingHabitRowView: View {
|
|||||||
SymbolIcon(symbolName, size: .row, color: isCompleted ? AppStatus.success : AppAccent.primary)
|
SymbolIcon(symbolName, size: .row, color: isCompleted ? AppStatus.success : AppAccent.primary)
|
||||||
.frame(width: AppMetrics.Size.iconLarge)
|
.frame(width: AppMetrics.Size.iconLarge)
|
||||||
|
|
||||||
StyledLabel(title, .body, emphasis: .primary)
|
Text(title).styled(.body, emphasis: .primary)
|
||||||
|
|
||||||
Spacer(minLength: Design.Spacing.medium)
|
Spacer(minLength: Design.Spacing.medium)
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,9 @@ struct RitualPreviewStepView: View {
|
|||||||
.frame(height: Design.Spacing.large)
|
.frame(height: Design.Spacing.large)
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
StyledLabel(headerText, .title2Bold, emphasis: .primary, alignment: .center)
|
Text(headerText)
|
||||||
|
.styled(.title2Bold, emphasis: .primary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
.opacity(animateContent ? 1 : 0)
|
.opacity(animateContent ? 1 : 0)
|
||||||
|
|
||||||
// Ritual preview card
|
// Ritual preview card
|
||||||
@ -36,9 +38,9 @@ struct RitualPreviewStepView: View {
|
|||||||
SymbolIcon(preset.iconName, size: .card, color: AppAccent.primary)
|
SymbolIcon(preset.iconName, size: .card, color: AppAccent.primary)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||||
StyledLabel(preset.title, .title3Bold, emphasis: .primary)
|
Text(preset.title).styled(.title3Bold, emphasis: .primary)
|
||||||
|
|
||||||
StyledLabel(preset.theme, .subheading, emphasis: .secondary)
|
Text(preset.theme).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ struct RitualPreviewStepView: View {
|
|||||||
SymbolIcon(habit.symbolName, size: .inline, color: AppTextColors.secondary)
|
SymbolIcon(habit.symbolName, size: .inline, color: AppTextColors.secondary)
|
||||||
.frame(width: 24)
|
.frame(width: 24)
|
||||||
|
|
||||||
StyledLabel(habit.title, .body, emphasis: .primary)
|
Text(habit.title).styled(.body, emphasis: .primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,9 +64,11 @@ struct RitualPreviewStepView: View {
|
|||||||
|
|
||||||
// Duration and time
|
// Duration and time
|
||||||
HStack(spacing: Design.Spacing.large) {
|
HStack(spacing: Design.Spacing.large) {
|
||||||
IconLabel("calendar", String(localized: "\(preset.durationDays) days"), .caption, emphasis: .secondary)
|
Label(String(localized: "\(preset.durationDays) days"), systemImage: "calendar")
|
||||||
|
.styled(.caption, emphasis: .secondary)
|
||||||
|
|
||||||
IconLabel(preset.timeOfDay.symbolName, preset.timeOfDay.displayName, .caption, emphasis: .secondary)
|
Label(preset.timeOfDay.displayName, systemImage: preset.timeOfDay.symbolName)
|
||||||
|
.styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
@ -80,7 +84,7 @@ struct RitualPreviewStepView: View {
|
|||||||
VStack(spacing: Design.Spacing.medium) {
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
// Primary CTA
|
// Primary CTA
|
||||||
Button(action: onStartRitual) {
|
Button(action: onStartRitual) {
|
||||||
StyledLabel(String(localized: "Start This Ritual"), .heading, emphasis: .inverse)
|
Text(String(localized: "Start This Ritual")).styled(.heading, emphasis: .inverse)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(height: AppMetrics.Size.buttonHeight)
|
.frame(height: AppMetrics.Size.buttonHeight)
|
||||||
.background(AppAccent.primary)
|
.background(AppAccent.primary)
|
||||||
@ -89,7 +93,7 @@ struct RitualPreviewStepView: View {
|
|||||||
|
|
||||||
// Skip option
|
// Skip option
|
||||||
Button(action: onSkip) {
|
Button(action: onSkip) {
|
||||||
StyledLabel(String(localized: "Skip for now"), .subheading, emphasis: .secondary)
|
Text(String(localized: "Skip for now")).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, Design.Spacing.xxLarge)
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||||
|
|||||||
@ -62,7 +62,7 @@ struct WhatsNextStepView: View {
|
|||||||
|
|
||||||
// CTA button
|
// CTA button
|
||||||
Button(action: onComplete) {
|
Button(action: onComplete) {
|
||||||
StyledLabel(String(localized: "Let's Go"), .heading, emphasis: .inverse)
|
Text(String(localized: "Let's Go")).styled(.heading, emphasis: .inverse)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(height: AppMetrics.Size.buttonHeight)
|
.frame(height: AppMetrics.Size.buttonHeight)
|
||||||
.background(AppAccent.primary)
|
.background(AppAccent.primary)
|
||||||
@ -99,9 +99,9 @@ private struct FeatureHelpCard: View {
|
|||||||
|
|
||||||
// Text
|
// Text
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||||
StyledLabel(title, .heading, emphasis: .primary)
|
Text(title).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
StyledLabel(description, .caption, emphasis: .secondary)
|
Text(description).styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@ -29,7 +29,7 @@ struct HabitPerformanceView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(String(localized: "Habit Performance"), .heading, emphasis: .primary)
|
Text(String(localized: "Habit Performance")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
LazyVGrid(columns: habitColumns, alignment: .leading, spacing: Design.Spacing.xSmall) {
|
LazyVGrid(columns: habitColumns, alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||||
ForEach(sortedByRate, id: \.habit.id) { item in
|
ForEach(sortedByRate, id: \.habit.id) { item in
|
||||||
@ -50,11 +50,11 @@ struct HabitPerformanceView: View {
|
|||||||
SymbolIcon(habit.symbolName, size: .inline, color: colorForRate(rate))
|
SymbolIcon(habit.symbolName, size: .inline, color: colorForRate(rate))
|
||||||
.frame(width: AppMetrics.Size.iconMedium)
|
.frame(width: AppMetrics.Size.iconMedium)
|
||||||
|
|
||||||
StyledLabel(habit.title, .subheading, emphasis: .primary)
|
Text(habit.title).styled(.subheading, emphasis: .primary)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
StyledLabel("\(Int(rate * 100))%", .subheadingEmphasis, emphasis: .custom(colorForRate(rate)))
|
Text("\(Int(rate * 100))%").styled(.subheadingEmphasis, emphasis: .custom(colorForRate(rate)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress bar
|
// Progress bar
|
||||||
|
|||||||
@ -58,7 +58,7 @@ struct RitualCardView: View {
|
|||||||
.typography(.subheading)
|
.typography(.subheading)
|
||||||
.foregroundStyle(hasActiveArc ? AppTextColors.secondary : AppTextColors.tertiary)
|
.foregroundStyle(hasActiveArc ? AppTextColors.secondary : AppTextColors.tertiary)
|
||||||
|
|
||||||
StyledLabel(completionSummary, .caption, emphasis: .secondary)
|
Text(completionSummary).styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
.background(AppSurface.card)
|
.background(AppSurface.card)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ struct RitualMilestonesView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(String(localized: "Milestones"), .heading, emphasis: .primary)
|
Text(String(localized: "Milestones")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
HStack(spacing: Design.Spacing.small) {
|
HStack(spacing: Design.Spacing.small) {
|
||||||
ForEach(milestones) { milestone in
|
ForEach(milestones) { milestone in
|
||||||
@ -38,9 +38,11 @@ struct RitualMilestonesView: View {
|
|||||||
SymbolIcon(milestone.symbolName, size: .badge, color: milestone.isAchieved ? AppStatus.success : AppTextColors.tertiary)
|
SymbolIcon(milestone.symbolName, size: .badge, color: milestone.isAchieved ? AppStatus.success : AppTextColors.tertiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledLabel("Day \(milestone.day)", .caption2, emphasis: .custom(milestone.isAchieved ? AppTextColors.primary : AppTextColors.tertiary))
|
Text("Day \(milestone.day)").styled(.caption2, emphasis: .custom(milestone.isAchieved ? AppTextColors.primary : AppTextColors.tertiary))
|
||||||
|
|
||||||
StyledLabel(milestone.title, .caption2, emphasis: .custom(milestone.isAchieved ? AppTextColors.secondary : AppTextColors.tertiary), lineLimit: 1)
|
Text(milestone.title)
|
||||||
|
.styled(.caption2, emphasis: .custom(milestone.isAchieved ? AppTextColors.secondary : AppTextColors.tertiary))
|
||||||
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
|
|||||||
@ -40,7 +40,7 @@ struct RitualProgressStatsView: View {
|
|||||||
ProgressView(value: progress)
|
ProgressView(value: progress)
|
||||||
.tint(AppAccent.primary)
|
.tint(AppAccent.primary)
|
||||||
|
|
||||||
StyledLabel("\(Int(progress * 100))% complete", .caption, emphasis: .secondary)
|
Text("\(Int(progress * 100))% complete").styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
@ -51,12 +51,12 @@ struct RitualProgressStatsView: View {
|
|||||||
|
|
||||||
private func statColumn(value: String, secondary: String?, label: String) -> some View {
|
private func statColumn(value: String, secondary: String?, label: String) -> some View {
|
||||||
VStack(spacing: Design.Spacing.xSmall) {
|
VStack(spacing: Design.Spacing.xSmall) {
|
||||||
StyledLabel(value, .titleBold, emphasis: .primary)
|
Text(value).styled(.titleBold, emphasis: .primary)
|
||||||
|
|
||||||
// Always show secondary row for consistent height
|
// Always show secondary row for consistent height
|
||||||
StyledLabel(secondary ?? " ", .caption, emphasis: .custom(secondary != nil ? AppTextColors.tertiary : .clear))
|
Text(secondary ?? " ").styled(.caption, emphasis: .custom(secondary != nil ? AppTextColors.tertiary : .clear))
|
||||||
|
|
||||||
StyledLabel(label, .caption2, emphasis: .secondary)
|
Text(label).styled(.caption2, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,7 +108,7 @@ struct RitualDetailView: View {
|
|||||||
if !ritual.notes.isEmpty {
|
if !ritual.notes.isEmpty {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||||
SectionHeaderView(title: String(localized: "Notes"))
|
SectionHeaderView(title: String(localized: "Notes"))
|
||||||
StyledLabel(ritual.notes, .body, emphasis: .secondary)
|
Text(ritual.notes).styled(.body, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ struct RitualDetailView: View {
|
|||||||
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
||||||
HStack {
|
HStack {
|
||||||
SymbolIcon("clock.arrow.circlepath", size: .inline, color: AppTextColors.secondary)
|
SymbolIcon("clock.arrow.circlepath", size: .inline, color: AppTextColors.secondary)
|
||||||
StyledLabel(String(localized: "This ritual is not currently active"), .subheading, emphasis: .secondary)
|
Text(String(localized: "This ritual is not currently active")).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let lastArc = ritual.latestArc {
|
if let lastArc = ritual.latestArc {
|
||||||
@ -266,7 +266,7 @@ struct RitualDetailView: View {
|
|||||||
let possibleCheckIns = habits.count * lastArc.durationDays
|
let possibleCheckIns = habits.count * lastArc.durationDays
|
||||||
let completionRate = possibleCheckIns > 0 ? Int(Double(totalCheckIns) / Double(possibleCheckIns) * 100) : 0
|
let completionRate = possibleCheckIns > 0 ? Int(Double(totalCheckIns) / Double(possibleCheckIns) * 100) : 0
|
||||||
|
|
||||||
StyledLabel(String(localized: "Last arc completed with \(completionRate)% habit completion over \(lastArc.durationDays) days."), .caption, emphasis: .tertiary)
|
Text(String(localized: "Last arc completed with \(completionRate)% habit completion over \(lastArc.durationDays) days.")).styled(.caption, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@ -312,13 +312,13 @@ struct RitualDetailView: View {
|
|||||||
HStack(spacing: Design.Spacing.medium) {
|
HStack(spacing: Design.Spacing.medium) {
|
||||||
// Current arc indicator
|
// Current arc indicator
|
||||||
if let arc = ritual.currentArc {
|
if let arc = ritual.currentArc {
|
||||||
StyledLabel(String(localized: "Arc \(arc.arcNumber)"), .captionEmphasis, emphasis: .custom(AppAccent.primary))
|
Text(String(localized: "Arc \(arc.arcNumber)")).styled(.captionEmphasis, emphasis: .custom(AppAccent.primary))
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
.padding(.vertical, Design.Spacing.xSmall)
|
.padding(.vertical, Design.Spacing.xSmall)
|
||||||
.background(AppAccent.primary.opacity(0.2))
|
.background(AppAccent.primary.opacity(0.2))
|
||||||
.clipShape(.capsule)
|
.clipShape(.capsule)
|
||||||
} else {
|
} else {
|
||||||
StyledLabel(String(localized: "No active arc"), .caption, emphasis: .tertiary)
|
Text(String(localized: "No active arc")).styled(.caption, emphasis: .tertiary)
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
.padding(.vertical, Design.Spacing.xSmall)
|
.padding(.vertical, Design.Spacing.xSmall)
|
||||||
.background(AppTextColors.tertiary.opacity(0.2))
|
.background(AppTextColors.tertiary.opacity(0.2))
|
||||||
@ -331,7 +331,7 @@ struct RitualDetailView: View {
|
|||||||
// Category badge (if set)
|
// Category badge (if set)
|
||||||
if !ritual.category.isEmpty {
|
if !ritual.category.isEmpty {
|
||||||
let badgeColor = categoryStore.color(for: ritual.category)
|
let badgeColor = categoryStore.color(for: ritual.category)
|
||||||
StyledLabel(ritual.category, .caption, emphasis: .custom(badgeColor))
|
Text(ritual.category).styled(.caption, emphasis: .custom(badgeColor))
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
.padding(.vertical, Design.Spacing.xSmall)
|
.padding(.vertical, Design.Spacing.xSmall)
|
||||||
.background(badgeColor.opacity(0.15))
|
.background(badgeColor.opacity(0.15))
|
||||||
@ -344,9 +344,10 @@ struct RitualDetailView: View {
|
|||||||
|
|
||||||
private var timeOfDayBadge: some View {
|
private var timeOfDayBadge: some View {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
IconLabel(ritual.timeOfDay.symbolName, ritual.timeOfDay.displayName, .caption2, emphasis: .custom(timeOfDayColor))
|
Label(ritual.timeOfDay.displayName, systemImage: ritual.timeOfDay.symbolName)
|
||||||
StyledLabel(" : ", .caption2, emphasis: .custom(timeOfDayColor))
|
.styled(.caption2, emphasis: .custom(timeOfDayColor))
|
||||||
StyledLabel(ritual.timeOfDay.timeRange, .caption2, emphasis: .custom(timeOfDayColor))
|
Text(" : ").styled(.caption2, emphasis: .custom(timeOfDayColor))
|
||||||
|
Text(ritual.timeOfDay.timeRange).styled(.caption2, emphasis: .custom(timeOfDayColor))
|
||||||
}
|
}
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
.padding(.vertical, Design.Spacing.xSmall)
|
.padding(.vertical, Design.Spacing.xSmall)
|
||||||
@ -381,7 +382,7 @@ struct RitualDetailView: View {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if completedArcs.isEmpty {
|
if completedArcs.isEmpty {
|
||||||
StyledLabel(String(localized: "No completed arcs yet."), .caption, emphasis: .tertiary)
|
Text(String(localized: "No completed arcs yet.")).styled(.caption, emphasis: .tertiary)
|
||||||
} else {
|
} else {
|
||||||
LazyVGrid(columns: arcColumns, alignment: .leading, spacing: Design.Spacing.small) {
|
LazyVGrid(columns: arcColumns, alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
ForEach(completedArcs) { arc in
|
ForEach(completedArcs) { arc in
|
||||||
@ -407,17 +408,17 @@ struct RitualDetailView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||||
StyledLabel(String(localized: "Arc \(arc.arcNumber)"), .subheadingEmphasis, emphasis: .primary)
|
Text(String(localized: "Arc \(arc.arcNumber)")).styled(.subheadingEmphasis, emphasis: .primary)
|
||||||
|
|
||||||
StyledLabel("\(dateFormatter.string(from: arc.startDate)) – \(dateFormatter.string(from: arc.endDate))", .caption, emphasis: .tertiary)
|
Text("\(dateFormatter.string(from: arc.startDate)) – \(dateFormatter.string(from: arc.endDate))").styled(.caption, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(alignment: .trailing, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .trailing, spacing: Design.Spacing.xSmall) {
|
||||||
StyledLabel("\(completionRate)%", .subheadingEmphasis, emphasis: .custom(completionRate >= 70 ? AppStatus.success : AppTextColors.secondary))
|
Text("\(completionRate)%").styled(.subheadingEmphasis, emphasis: .custom(completionRate >= 70 ? AppStatus.success : AppTextColors.secondary))
|
||||||
|
|
||||||
StyledLabel(String(localized: "\(arc.durationDays) days"), .caption, emphasis: .tertiary)
|
Text(String(localized: "\(arc.durationDays) days")).styled(.caption, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
SymbolIcon.chevron(color: AppTextColors.tertiary)
|
SymbolIcon.chevron(color: AppTextColors.tertiary)
|
||||||
@ -436,13 +437,15 @@ struct RitualDetailView: View {
|
|||||||
HStack(spacing: Design.Spacing.medium) {
|
HStack(spacing: Design.Spacing.medium) {
|
||||||
// Days remaining
|
// Days remaining
|
||||||
if daysRemaining > 0 {
|
if daysRemaining > 0 {
|
||||||
IconLabel("hourglass", String(localized: "\(daysRemaining) days remaining"), .caption, emphasis: .secondary)
|
Label(String(localized: "\(daysRemaining) days remaining"), systemImage: "hourglass")
|
||||||
|
.styled(.caption, emphasis: .secondary)
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
.background(AppSurface.card)
|
.background(AppSurface.card)
|
||||||
.clipShape(.capsule)
|
.clipShape(.capsule)
|
||||||
} else {
|
} else {
|
||||||
IconLabel("checkmark.seal.fill", String(localized: "Arc complete!"), .caption, emphasis: .custom(AppStatus.success))
|
Label(String(localized: "Arc complete!"), systemImage: "checkmark.seal.fill")
|
||||||
|
.styled(.caption, emphasis: .custom(AppStatus.success))
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
.background(AppStatus.success.opacity(Design.Opacity.subtle))
|
.background(AppStatus.success.opacity(Design.Opacity.subtle))
|
||||||
@ -451,7 +454,8 @@ struct RitualDetailView: View {
|
|||||||
|
|
||||||
// Ritual streak
|
// Ritual streak
|
||||||
if ritualStreak > 0 {
|
if ritualStreak > 0 {
|
||||||
IconLabel("flame.fill", String(localized: "\(ritualStreak)-day streak"), .caption, emphasis: .custom(AppStatus.success))
|
Label(String(localized: "\(ritualStreak)-day streak"), systemImage: "flame.fill")
|
||||||
|
.styled(.caption, emphasis: .custom(AppStatus.success))
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
.background(AppStatus.success.opacity(Design.Opacity.subtle))
|
.background(AppStatus.success.opacity(Design.Opacity.subtle))
|
||||||
@ -464,7 +468,8 @@ struct RitualDetailView: View {
|
|||||||
// Arc comparison (only shown if there's a previous arc)
|
// Arc comparison (only shown if there's a previous arc)
|
||||||
if let comparison = arcComparisonInfo {
|
if let comparison = arcComparisonInfo {
|
||||||
let comparisonColor = comparison.isAhead ? AppStatus.success : comparison.isBehind ? AppStatus.warning : AppTextColors.secondary
|
let comparisonColor = comparison.isAhead ? AppStatus.success : comparison.isBehind ? AppStatus.warning : AppTextColors.secondary
|
||||||
IconLabel(comparison.isAhead ? "arrow.up.right" : comparison.isBehind ? "arrow.down.right" : "equal", comparison.text, .caption, emphasis: .custom(comparisonColor))
|
Label(comparison.text, systemImage: comparison.isAhead ? "arrow.up.right" : comparison.isBehind ? "arrow.down.right" : "equal")
|
||||||
|
.styled(.caption, emphasis: .custom(comparisonColor))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,7 +141,7 @@ struct RitualsView: View {
|
|||||||
// Time of day header
|
// Time of day header
|
||||||
HStack(spacing: Design.Spacing.small) {
|
HStack(spacing: Design.Spacing.small) {
|
||||||
SymbolIcon(group.timeOfDay.symbolName, size: .inline, color: AppAccent.primary)
|
SymbolIcon(group.timeOfDay.symbolName, size: .inline, color: AppAccent.primary)
|
||||||
StyledLabel(group.timeOfDay.displayName, .subheadingEmphasis, emphasis: .secondary)
|
Text(group.timeOfDay.displayName).styled(.subheadingEmphasis, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
.padding(.top, Design.Spacing.small)
|
.padding(.top, Design.Spacing.small)
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ struct RitualsView: View {
|
|||||||
VStack(spacing: Design.Spacing.large) {
|
VStack(spacing: Design.Spacing.large) {
|
||||||
SymbolIcon("sparkles", size: .hero, color: AppAccent.primary)
|
SymbolIcon("sparkles", size: .hero, color: AppAccent.primary)
|
||||||
|
|
||||||
StyledLabel(String(localized: "No Active Rituals"), .heading, emphasis: .primary)
|
Text(String(localized: "No Active Rituals")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
Text(String(localized: "Create a custom ritual or browse our preset library to get started."))
|
Text(String(localized: "Create a custom ritual or browse our preset library to get started."))
|
||||||
.typography(.subheading)
|
.typography(.subheading)
|
||||||
@ -217,7 +217,7 @@ struct RitualsView: View {
|
|||||||
VStack(spacing: Design.Spacing.large) {
|
VStack(spacing: Design.Spacing.large) {
|
||||||
SymbolIcon("clock.arrow.circlepath", size: .hero, color: AppTextColors.tertiary)
|
SymbolIcon("clock.arrow.circlepath", size: .hero, color: AppTextColors.tertiary)
|
||||||
|
|
||||||
StyledLabel(String(localized: "No Past Rituals"), .heading, emphasis: .primary)
|
Text(String(localized: "No Past Rituals")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
Text(String(localized: "Rituals that have ended will appear here. You can restart them anytime."))
|
Text(String(localized: "Rituals that have ended will appear here. You can restart them anytime."))
|
||||||
.typography(.subheading)
|
.typography(.subheading)
|
||||||
@ -324,12 +324,13 @@ struct RitualsView: View {
|
|||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.small))
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.small))
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||||
StyledLabel(ritual.title, .heading, emphasis: .primary)
|
Text(ritual.title).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
StyledLabel(ritual.theme, .caption, emphasis: .secondary)
|
Text(ritual.theme).styled(.caption, emphasis: .secondary)
|
||||||
|
|
||||||
if let lastArc = ritual.latestArc {
|
if let lastArc = ritual.latestArc {
|
||||||
IconLabel("calendar", formattedEndDate(lastArc.endDate), .caption2, emphasis: .tertiary)
|
Label(formattedEndDate(lastArc.endDate), systemImage: "calendar")
|
||||||
|
.styled(.caption2, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,7 +338,7 @@ struct RitualsView: View {
|
|||||||
|
|
||||||
// Arc count badge
|
// Arc count badge
|
||||||
if ritual.completedArcCount > 0 {
|
if ritual.completedArcCount > 0 {
|
||||||
StyledLabel("\(ritual.completedArcCount) arc\(ritual.completedArcCount == 1 ? "" : "s")", .caption2, emphasis: .secondary)
|
Text("\(ritual.completedArcCount) arc\(ritual.completedArcCount == 1 ? "" : "s")").styled(.caption2, emphasis: .secondary)
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
.padding(.vertical, Design.Spacing.xSmall)
|
.padding(.vertical, Design.Spacing.xSmall)
|
||||||
.background(AppSurface.secondary)
|
.background(AppSurface.secondary)
|
||||||
|
|||||||
@ -60,7 +60,8 @@ struct PresetLibrarySheet: View {
|
|||||||
selectedCategory = category
|
selectedCategory = category
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
IconLabel(category.symbolName, category.displayName, .subheading, emphasis: .custom(selectedCategory == category ? .white : AppTextColors.primary))
|
Label(category.displayName, systemImage: category.symbolName)
|
||||||
|
.styled(.subheading, emphasis: .custom(selectedCategory == category ? .white : AppTextColors.primary))
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
.background(selectedCategory == category ? AppAccent.primary : AppSurface.card)
|
.background(selectedCategory == category ? AppAccent.primary : AppSurface.card)
|
||||||
@ -92,14 +93,14 @@ struct PresetLibrarySheet: View {
|
|||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||||
HStack {
|
HStack {
|
||||||
StyledLabel(preset.title, .heading, emphasis: .primary)
|
Text(preset.title).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
if isAdded {
|
if isAdded {
|
||||||
SymbolIcon("checkmark.circle.fill", size: .badge, color: AppStatus.success)
|
SymbolIcon("checkmark.circle.fill", size: .badge, color: AppStatus.success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledLabel(preset.theme, .subheading, emphasis: .secondary)
|
Text(preset.theme).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -107,7 +108,7 @@ struct PresetLibrarySheet: View {
|
|||||||
VStack(alignment: .trailing, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .trailing, spacing: Design.Spacing.xSmall) {
|
||||||
SymbolIcon(preset.timeOfDay.symbolName, size: .badge, color: AppTextColors.tertiary)
|
SymbolIcon(preset.timeOfDay.symbolName, size: .badge, color: AppTextColors.tertiary)
|
||||||
|
|
||||||
StyledLabel(String(localized: "\(preset.habits.count) habits"), .caption, emphasis: .tertiary)
|
Text(String(localized: "\(preset.habits.count) habits")).styled(.caption, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ struct PresetLibrarySheet: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if preset.habits.count > 4 {
|
if preset.habits.count > 4 {
|
||||||
StyledLabel("+\(preset.habits.count - 4)", .caption2, emphasis: .tertiary)
|
Text("+\(preset.habits.count - 4)").styled(.caption2, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,9 +198,9 @@ struct PresetDetailSheet: View {
|
|||||||
|
|
||||||
private var descriptionSection: some View {
|
private var descriptionSection: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(String(localized: "About"), .heading, emphasis: .primary)
|
Text(String(localized: "About")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
StyledLabel(preset.notes, .body, emphasis: .secondary)
|
Text(preset.notes).styled(.body, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
@ -209,7 +210,7 @@ struct PresetDetailSheet: View {
|
|||||||
|
|
||||||
private var habitsSection: some View {
|
private var habitsSection: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(String(localized: "Habits"), .heading, emphasis: .primary)
|
Text(String(localized: "Habits")).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
VStack(spacing: Design.Spacing.xSmall) {
|
VStack(spacing: Design.Spacing.xSmall) {
|
||||||
ForEach(preset.habits) { habit in
|
ForEach(preset.habits) { habit in
|
||||||
@ -217,7 +218,7 @@ struct PresetDetailSheet: View {
|
|||||||
SymbolIcon(habit.symbolName, size: .inline, color: AppAccent.primary)
|
SymbolIcon(habit.symbolName, size: .inline, color: AppAccent.primary)
|
||||||
.frame(width: 24)
|
.frame(width: 24)
|
||||||
|
|
||||||
StyledLabel(habit.title, .subheading, emphasis: .primary)
|
Text(habit.title).styled(.subheading, emphasis: .primary)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@ -241,12 +242,11 @@ struct PresetDetailSheet: View {
|
|||||||
store.createRitualFromPreset(preset)
|
store.createRitualFromPreset(preset)
|
||||||
hasBeenAdded = true
|
hasBeenAdded = true
|
||||||
} label: {
|
} label: {
|
||||||
IconLabel(
|
Label(
|
||||||
isAlreadyAdded || hasBeenAdded ? "checkmark.circle.fill" : "plus.circle.fill",
|
|
||||||
isAlreadyAdded || hasBeenAdded ? String(localized: "Added to My Rituals") : String(localized: "Add to My Rituals"),
|
isAlreadyAdded || hasBeenAdded ? String(localized: "Added to My Rituals") : String(localized: "Add to My Rituals"),
|
||||||
.heading,
|
systemImage: isAlreadyAdded || hasBeenAdded ? "checkmark.circle.fill" : "plus.circle.fill"
|
||||||
emphasis: .custom(.white)
|
|
||||||
)
|
)
|
||||||
|
.styled(.heading, emphasis: .custom(.white))
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(Design.Spacing.medium)
|
.padding(Design.Spacing.medium)
|
||||||
.background(isAlreadyAdded || hasBeenAdded ? AppStatus.success : AppAccent.primary)
|
.background(isAlreadyAdded || hasBeenAdded ? AppStatus.success : AppAccent.primary)
|
||||||
|
|||||||
@ -122,7 +122,7 @@ struct RitualEditSheet: View {
|
|||||||
|
|
||||||
// Category selection - simple picker from CategoryStore
|
// Category selection - simple picker from CategoryStore
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
||||||
StyledLabel(String(localized: "Category"), .subheading, emphasis: .secondary)
|
Text(String(localized: "Category")).styled(.subheading, emphasis: .secondary)
|
||||||
|
|
||||||
// Horizontal scrollable category chips
|
// Horizontal scrollable category chips
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
@ -169,7 +169,8 @@ struct RitualEditSheet: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show the time range for the selected time of day
|
// Show the time range for the selected time of day
|
||||||
IconLabel(timeOfDay.symbolName, timeOfDay.timeRange, .caption, emphasis: .tertiary)
|
Label(timeOfDay.timeRange, systemImage: timeOfDay.symbolName)
|
||||||
|
.styled(.caption, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
.listRowBackground(AppSurface.card)
|
.listRowBackground(AppSurface.card)
|
||||||
|
|
||||||
@ -225,7 +226,7 @@ struct RitualEditSheet: View {
|
|||||||
Button {
|
Button {
|
||||||
durationDays = Double(days)
|
durationDays = Double(days)
|
||||||
} label: {
|
} label: {
|
||||||
StyledLabel("\(days)", .caption, emphasis: .custom(Int(durationDays) == days ? AppAccent.primary : AppTextColors.tertiary))
|
Text("\(days)").styled(.caption, emphasis: .custom(Int(durationDays) == days ? AppAccent.primary : AppTextColors.tertiary))
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
.padding(.vertical, Design.Spacing.xSmall)
|
.padding(.vertical, Design.Spacing.xSmall)
|
||||||
.background(Int(durationDays) == days ? AppAccent.primary.opacity(0.2) : AppSurface.secondary)
|
.background(Int(durationDays) == days ? AppAccent.primary.opacity(0.2) : AppSurface.secondary)
|
||||||
@ -241,7 +242,7 @@ struct RitualEditSheet: View {
|
|||||||
} header: {
|
} header: {
|
||||||
Text(String(localized: "Schedule"))
|
Text(String(localized: "Schedule"))
|
||||||
} footer: {
|
} footer: {
|
||||||
StyledLabel(String(localized: "Tap the duration to enter a custom number of days (up to 365)."), .caption, emphasis: .secondary)
|
Text(String(localized: "Tap the duration to enter a custom number of days (up to 365).")).styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,11 +315,11 @@ struct RitualEditSheet: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Text(String(localized: "Habits"))
|
Text(String(localized: "Habits"))
|
||||||
Spacer()
|
Spacer()
|
||||||
StyledLabel(String(localized: "\(habits.count) habits"), .caption, emphasis: .tertiary)
|
Text(String(localized: "\(habits.count) habits")).styled(.caption, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
} footer: {
|
} footer: {
|
||||||
if habits.count > 1 {
|
if habits.count > 1 {
|
||||||
StyledLabel(String(localized: "Drag the handle to reorder habits."), .caption, emphasis: .secondary)
|
Text(String(localized: "Drag the handle to reorder habits.")).styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,7 +378,7 @@ struct RitualEditSheet: View {
|
|||||||
.fill(color)
|
.fill(color)
|
||||||
.frame(width: 10, height: 10)
|
.frame(width: 10, height: 10)
|
||||||
}
|
}
|
||||||
StyledLabel(label, .subheading, emphasis: .custom(isSelected ? .white : AppTextColors.primary))
|
Text(label).styled(.subheading, emphasis: .custom(isSelected ? .white : AppTextColors.primary))
|
||||||
}
|
}
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
@ -493,7 +494,7 @@ struct IconPickerSheet: View {
|
|||||||
LazyVStack(alignment: .leading, spacing: Design.Spacing.large) {
|
LazyVStack(alignment: .leading, spacing: Design.Spacing.large) {
|
||||||
ForEach(iconGroups, id: \.name) { group in
|
ForEach(iconGroups, id: \.name) { group in
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(group.name, .caption, emphasis: .secondary)
|
Text(group.name).styled(.caption, emphasis: .secondary)
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
|
|
||||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: Design.Spacing.small) {
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: Design.Spacing.small) {
|
||||||
@ -597,7 +598,7 @@ struct HabitIconPickerSheet: View {
|
|||||||
LazyVStack(alignment: .leading, spacing: Design.Spacing.large) {
|
LazyVStack(alignment: .leading, spacing: Design.Spacing.large) {
|
||||||
ForEach(iconGroups, id: \.name) { group in
|
ForEach(iconGroups, id: \.name) { group in
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
StyledLabel(group.name, .caption, emphasis: .secondary)
|
Text(group.name).styled(.caption, emphasis: .secondary)
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
|
|
||||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: Design.Spacing.small) {
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: Design.Spacing.small) {
|
||||||
|
|||||||
@ -33,7 +33,7 @@ struct RitualFocusCardView: View {
|
|||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
StyledLabel(title, .heading, emphasis: .primary)
|
Text(title).styled(.heading, emphasis: .primary)
|
||||||
|
|
||||||
Spacer(minLength: Design.Spacing.medium)
|
Spacer(minLength: Design.Spacing.medium)
|
||||||
|
|
||||||
@ -48,12 +48,12 @@ struct RitualFocusCardView: View {
|
|||||||
.accessibilityLabel(Text(dayLabel))
|
.accessibilityLabel(Text(dayLabel))
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledLabel(theme, .subheading, emphasis: .secondary)
|
Text(theme).styled(.subheading, emphasis: .secondary)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||||
ProgressView(value: progress)
|
ProgressView(value: progress)
|
||||||
.tint(AppAccent.primary)
|
.tint(AppAccent.primary)
|
||||||
StyledLabel(completionSummary, .caption, emphasis: .secondary)
|
Text(completionSummary).styled(.caption, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
|
|||||||
@ -29,7 +29,7 @@ struct TodayHabitRowView: View {
|
|||||||
.frame(width: AppMetrics.Size.iconLarge)
|
.frame(width: AppMetrics.Size.iconLarge)
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
|
|
||||||
StyledLabel(title, .subheading, emphasis: .primary)
|
Text(title).styled(.subheading, emphasis: .primary)
|
||||||
|
|
||||||
Spacer(minLength: Design.Spacing.medium)
|
Spacer(minLength: Design.Spacing.medium)
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,8 @@ struct TodayHeaderView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
|
||||||
StyledLabel(String(localized: "Today"), .heroBold, emphasis: .primary)
|
Text(String(localized: "Today")).styled(.heroBold, emphasis: .primary)
|
||||||
StyledLabel(dateText, .subheading, emphasis: .secondary)
|
Text(dateText).styled(.subheading, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user