From 90366720c01055981fd770466a833bf48dd7076d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 27 Jan 2026 11:16:09 -0600 Subject: [PATCH] Signed-off-by: Matt Bruce --- .../App/Localization/Localizable.xcstrings | 36 ------ .../Components/InsightDetailSheet.swift | 67 +++------- .../Onboarding/FirstCheckInStepView.swift | 56 ++------ .../Onboarding/RitualPreviewStepView.swift | 53 ++------ .../Views/Onboarding/WhatsNextStepView.swift | 17 +-- .../Components/HabitPerformanceView.swift | 16 +-- .../Components/RitualMilestonesView.swift | 18 +-- .../Components/RitualProgressStatsView.swift | 17 +-- .../App/Views/Rituals/RitualDetailView.swift | 121 +++++------------- Andromida/App/Views/Rituals/RitualsView.swift | 35 ++--- .../Rituals/Sheets/PresetLibrarySheet.swift | 79 ++++-------- .../Rituals/Sheets/RitualEditSheet.swift | 58 +++------ 12 files changed, 138 insertions(+), 435 deletions(-) diff --git a/Andromida/App/Localization/Localizable.xcstrings b/Andromida/App/Localization/Localizable.xcstrings index c7968e4..3ee9c5f 100644 --- a/Andromida/App/Localization/Localizable.xcstrings +++ b/Andromida/App/Localization/Localizable.xcstrings @@ -3,27 +3,11 @@ "strings" : { "" : { - }, - " : " : { - "comment" : "A separator between the time of day and its range in the RitualDetailView.", - "isCommentAutoGenerated" : true }, "-%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.", "isCommentAutoGenerated" : true }, - "%@ – %@" : { - "comment" : "A sublabel that shows the start and end dates of an arc. The date format used is \"MMM d, yyyy\".", - "isCommentAutoGenerated" : true, - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ – %2$@" - } - } - } - }, "%@, 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.", "isCommentAutoGenerated" : true, @@ -40,18 +24,6 @@ "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 }, - "%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" : { "comment" : "A label displaying the duration of a preset in days. The argument is the number of days the preset is active.", "isCommentAutoGenerated" : true @@ -154,10 +126,6 @@ } } }, - "%lld%% complete" : { - "comment" : "A text label showing the percentage of the ritual that has been completed.", - "isCommentAutoGenerated" : true - }, "%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.", "isCommentAutoGenerated" : true, @@ -733,10 +701,6 @@ "comment" : "Label for the x-axis in the mini sparkline chart within an `InsightCardView`.", "isCommentAutoGenerated" : true }, - "Day %lld" : { - "comment" : "A small label displaying the day and title of a milestone. The first argument is the day of the milestone. The second argument is the title of the milestone.", - "isCommentAutoGenerated" : true - }, "Day %lld of %lld" : { "localizations" : { "en" : { diff --git a/Andromida/App/Views/Insights/Components/InsightDetailSheet.swift b/Andromida/App/Views/Insights/Components/InsightDetailSheet.swift index 956382b..3e68a28 100644 --- a/Andromida/App/Views/Insights/Components/InsightDetailSheet.swift +++ b/Andromida/App/Views/Insights/Components/InsightDetailSheet.swift @@ -101,18 +101,11 @@ struct InsightDetailSheet: View { private var trendIndicatorSection: some View { HStack(spacing: Design.Spacing.medium) { // Trend direction badge - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: trendDirection.symbolName) - .font(.caption) - - Text(trendDirection.accessibilityLabel) - .font(.caption) - } - .foregroundStyle(trendDirection.color) - .padding(.horizontal, Design.Spacing.medium) - .padding(.vertical, Design.Spacing.small) - .background(trendDirection.color.opacity(Design.Opacity.subtle)) - .clipShape(.capsule) + IconLabel(trendDirection.symbolName, trendDirection.accessibilityLabel, .caption, emphasis: .custom(trendDirection.color)) + .padding(.horizontal, Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) + .background(trendDirection.color.opacity(Design.Opacity.subtle)) + .clipShape(.capsule) // Week-over-week change if abs(weekOverWeekChange) > 0.01 { @@ -121,9 +114,7 @@ struct InsightDetailSheet: View { ? String(localized: "+\(changePercent)% vs last week") : String(localized: "-\(changePercent)% vs last week") - Text(changeText) - .font(.caption) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(changeText, .caption, emphasis: .custom(AppTextColors.secondary)) } Spacer() @@ -135,22 +126,15 @@ struct InsightDetailSheet: View { private var tipsSection: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { - Label(String(localized: "Tips"), systemImage: "lightbulb.fill") - .font(.headline) - .foregroundStyle(AppTextColors.primary) + IconLabel("lightbulb.fill", String(localized: "Tips"), .heading, emphasis: .custom(AppTextColors.primary)) VStack(alignment: .leading, spacing: Design.Spacing.small) { ForEach(tips, id: \.self) { tip in HStack(alignment: .top, spacing: Design.Spacing.small) { - Image(systemName: "chevron.right") - .font(.caption2) - .foregroundStyle(AppAccent.primary) + SymbolIcon("chevron.right", size: .badge, color: AppAccent.primary) .padding(.top, Design.Spacing.xSmall) - Text(tip) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) - .fixedSize(horizontal: false, vertical: true) + StyledLabel(tip, .subheading, emphasis: .custom(AppTextColors.secondary)) } } } @@ -165,14 +149,9 @@ struct InsightDetailSheet: View { private var explanationSection: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(String(localized: "What this means")) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "What this means"), .heading, emphasis: .custom(AppTextColors.primary)) - Text(card.explanation) - .font(.body) - .foregroundStyle(AppTextColors.secondary) - .fixedSize(horizontal: false, vertical: true) + StyledLabel(card.explanation, .body, emphasis: .custom(AppTextColors.secondary)) } .padding(Design.Spacing.large) .frame(maxWidth: .infinity, alignment: .leading) @@ -184,9 +163,7 @@ struct InsightDetailSheet: View { private func chartSection(_ data: [TrendDataPoint]) -> some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(String(localized: "7-Day Trend")) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "7-Day Trend"), .heading, emphasis: .custom(AppTextColors.primary)) Chart(data) { point in BarMark( @@ -205,8 +182,9 @@ struct InsightDetailSheet: View { AxisMarks(values: [0, 0.5, 1.0]) { value in AxisValueLabel { if let v = value.as(Double.self) { + // Exception: Inside AxisValueLabel closure Text("\(Int(v * 100))%") - .font(.caption2) + .font(Typography.caption2.font) .foregroundStyle(AppTextColors.tertiary) } } @@ -215,9 +193,10 @@ struct InsightDetailSheet: View { } } .chartXAxis { - AxisMarks { value in + AxisMarks { _ in + // Exception: AxisValueLabel doesn't support StyledLabel AxisValueLabel() - .font(.caption2) + .font(Typography.caption2.font) .foregroundStyle(AppTextColors.secondary) } } @@ -235,22 +214,16 @@ struct InsightDetailSheet: View { private func breakdownSection(_ breakdown: [BreakdownItem]) -> some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(String(localized: "Breakdown")) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "Breakdown"), .heading, emphasis: .custom(AppTextColors.primary)) VStack(spacing: Design.Spacing.xSmall) { ForEach(breakdown) { item in HStack { - Text(item.label) - .font(.subheadline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(item.label, .subheading, emphasis: .custom(AppTextColors.primary)) Spacer() - Text(item.value) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(item.value, .subheading, emphasis: .custom(AppTextColors.secondary)) } .padding(.vertical, Design.Spacing.small) diff --git a/Andromida/App/Views/Onboarding/FirstCheckInStepView.swift b/Andromida/App/Views/Onboarding/FirstCheckInStepView.swift index f4df4b0..4ea281d 100644 --- a/Andromida/App/Views/Onboarding/FirstCheckInStepView.swift +++ b/Andromida/App/Views/Onboarding/FirstCheckInStepView.swift @@ -55,29 +55,19 @@ struct FirstCheckInStepView: View { private var tutorialView: some View { VStack(spacing: Design.Spacing.xxLarge) { // Header - Text(String(localized: "Let's try it out!")) - .font(.title2) - .fontWeight(.bold) - .foregroundStyle(AppTextColors.primary) - .multilineTextAlignment(.center) + StyledLabel(String(localized: "Let's try it out!"), .title2Bold, emphasis: .custom(AppTextColors.primary), alignment: .center) .opacity(animateContent ? 1 : 0) // Ritual card with habits VStack(alignment: .leading, spacing: Design.Spacing.medium) { // Header HStack(spacing: Design.Spacing.small) { - Image(systemName: ritual.iconName) - .font(.headline) - .foregroundStyle(AppAccent.primary) + SymbolIcon(ritual.iconName, size: .row, color: AppAccent.primary) VStack(alignment: .leading, spacing: 2) { - Text(String(localized: "Day 1 of \(ritual.durationDays)")) - .font(.caption) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(String(localized: "Day 1 of \(ritual.durationDays)"), .caption, emphasis: .custom(AppTextColors.secondary)) - Text(ritual.title) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(ritual.title, .heading, emphasis: .custom(AppTextColors.primary)) } Spacer() @@ -103,18 +93,14 @@ struct FirstCheckInStepView: View { .offset(y: animateContent ? 0 : 20) // Instruction - Text(String(localized: "Tap a habit to check in")) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(String(localized: "Tap a habit to check in"), .subheading, emphasis: .custom(AppTextColors.secondary)) .opacity(animateContent ? 1 : 0) Spacer() VStack(spacing: Design.Spacing.medium) { Button(action: onComplete) { - Text(String(localized: "Continue")) - .font(.headline) - .foregroundStyle(AppTextColors.inverse) + StyledLabel(String(localized: "Continue"), .heading, emphasis: .custom(AppTextColors.inverse)) .frame(maxWidth: .infinity) .frame(height: AppMetrics.Size.buttonHeight) .background(AppAccent.primary) @@ -122,9 +108,7 @@ struct FirstCheckInStepView: View { } Button(action: onComplete) { - Text(String(localized: "Skip for now")) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(String(localized: "Skip for now"), .subheading, emphasis: .custom(AppTextColors.secondary)) } } .padding(.horizontal, Design.Spacing.xxLarge) @@ -151,15 +135,9 @@ struct FirstCheckInStepView: View { .opacity(showCelebration ? 1 : 0) VStack(spacing: Design.Spacing.medium) { - Text(String(localized: "Nice work!")) - .font(.largeTitle) - .fontWeight(.bold) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "Nice work!"), .heroBold, emphasis: .custom(AppTextColors.primary)) - Text(String(localized: "You completed your first check-in")) - .font(.title3) - .foregroundStyle(AppTextColors.secondary) - .multilineTextAlignment(.center) + StyledLabel(String(localized: "You completed your first check-in"), .title3, emphasis: .custom(AppTextColors.secondary), alignment: .center) } Spacer() @@ -167,9 +145,7 @@ struct FirstCheckInStepView: View { // Continue button if showContinueButton { Button(action: onComplete) { - Text(String(localized: "Continue to Rituals")) - .font(.headline) - .foregroundStyle(AppTextColors.inverse) + StyledLabel(String(localized: "Continue to Rituals"), .heading, emphasis: .custom(AppTextColors.inverse)) .frame(maxWidth: .infinity) .frame(height: AppMetrics.Size.buttonHeight) .background(AppAccent.primary) @@ -215,20 +191,14 @@ private struct OnboardingHabitRowView: View { var body: some View { Button(action: action) { HStack(spacing: Design.Spacing.medium) { - Image(systemName: symbolName) - .font(.title3) - .foregroundStyle(isCompleted ? AppStatus.success : AppAccent.primary) + SymbolIcon(symbolName, size: .row, color: isCompleted ? AppStatus.success : AppAccent.primary) .frame(width: AppMetrics.Size.iconLarge) - Text(title) - .font(.body) - .foregroundStyle(AppTextColors.primary) + StyledLabel(title, .body, emphasis: .custom(AppTextColors.primary)) Spacer(minLength: Design.Spacing.medium) - Image(systemName: isCompleted ? "checkmark.circle.fill" : "circle") - .font(.title3) - .foregroundStyle(isCompleted ? AppStatus.success : AppBorder.subtle) + SymbolIcon(isCompleted ? "checkmark.circle.fill" : "circle", size: .row, color: isCompleted ? AppStatus.success : AppBorder.subtle) } .padding(.horizontal, Design.Spacing.medium) .padding(.vertical, Design.Spacing.medium) diff --git a/Andromida/App/Views/Onboarding/RitualPreviewStepView.swift b/Andromida/App/Views/Onboarding/RitualPreviewStepView.swift index 0d8a0aa..130b078 100644 --- a/Andromida/App/Views/Onboarding/RitualPreviewStepView.swift +++ b/Andromida/App/Views/Onboarding/RitualPreviewStepView.swift @@ -26,30 +26,19 @@ struct RitualPreviewStepView: View { .frame(height: Design.Spacing.large) // Header - Text(headerText) - .font(.title2) - .fontWeight(.bold) - .foregroundStyle(AppTextColors.primary) - .multilineTextAlignment(.center) + StyledLabel(headerText, .title2Bold, emphasis: .custom(AppTextColors.primary), alignment: .center) .opacity(animateContent ? 1 : 0) // Ritual preview card VStack(alignment: .leading, spacing: Design.Spacing.large) { // Title and theme HStack(spacing: Design.Spacing.medium) { - Image(systemName: preset.iconName) - .font(.title) - .foregroundStyle(AppAccent.primary) + SymbolIcon(preset.iconName, size: .card, color: AppAccent.primary) VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { - Text(preset.title) - .font(.title3) - .fontWeight(.semibold) - .foregroundStyle(AppTextColors.primary) + StyledLabel(preset.title, .title3Bold, emphasis: .custom(AppTextColors.primary)) - Text(preset.theme) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(preset.theme, .subheading, emphasis: .custom(AppTextColors.secondary)) } } @@ -60,14 +49,10 @@ struct RitualPreviewStepView: View { VStack(alignment: .leading, spacing: Design.Spacing.small) { ForEach(preset.habits) { habit in HStack(spacing: Design.Spacing.small) { - Image(systemName: habit.symbolName) - .font(.body) - .foregroundStyle(AppTextColors.secondary) + SymbolIcon(habit.symbolName, size: .inline, color: AppTextColors.secondary) .frame(width: 24) - Text(habit.title) - .font(.body) - .foregroundStyle(AppTextColors.primary) + StyledLabel(habit.title, .body, emphasis: .custom(AppTextColors.primary)) } } } @@ -77,23 +62,9 @@ struct RitualPreviewStepView: View { // Duration and time HStack(spacing: Design.Spacing.large) { - Label { - Text(String(localized: "\(preset.durationDays) days")) - .font(.caption) - .foregroundStyle(AppTextColors.secondary) - } icon: { - Image(systemName: "calendar") - .foregroundStyle(AppTextColors.tertiary) - } + IconLabel("calendar", String(localized: "\(preset.durationDays) days"), .caption, emphasis: .custom(AppTextColors.secondary)) - Label { - Text(preset.timeOfDay.displayName) - .font(.caption) - .foregroundStyle(AppTextColors.secondary) - } icon: { - Image(systemName: preset.timeOfDay.symbolName) - .foregroundStyle(AppTextColors.tertiary) - } + IconLabel(preset.timeOfDay.symbolName, preset.timeOfDay.displayName, .caption, emphasis: .custom(AppTextColors.secondary)) } } .padding(Design.Spacing.large) @@ -109,9 +80,7 @@ struct RitualPreviewStepView: View { VStack(spacing: Design.Spacing.medium) { // Primary CTA Button(action: onStartRitual) { - Text(String(localized: "Start This Ritual")) - .font(.headline) - .foregroundStyle(AppTextColors.inverse) + StyledLabel(String(localized: "Start This Ritual"), .heading, emphasis: .custom(AppTextColors.inverse)) .frame(maxWidth: .infinity) .frame(height: AppMetrics.Size.buttonHeight) .background(AppAccent.primary) @@ -120,9 +89,7 @@ struct RitualPreviewStepView: View { // Skip option Button(action: onSkip) { - Text(String(localized: "Skip for now")) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(String(localized: "Skip for now"), .subheading, emphasis: .custom(AppTextColors.secondary)) } } .padding(.horizontal, Design.Spacing.xxLarge) diff --git a/Andromida/App/Views/Onboarding/WhatsNextStepView.swift b/Andromida/App/Views/Onboarding/WhatsNextStepView.swift index 620402b..e3a466b 100644 --- a/Andromida/App/Views/Onboarding/WhatsNextStepView.swift +++ b/Andromida/App/Views/Onboarding/WhatsNextStepView.swift @@ -62,9 +62,7 @@ struct WhatsNextStepView: View { // CTA button Button(action: onComplete) { - Text(String(localized: "Let's Go")) - .font(.headline) - .foregroundStyle(AppTextColors.inverse) + StyledLabel(String(localized: "Let's Go"), .heading, emphasis: .custom(AppTextColors.inverse)) .frame(maxWidth: .infinity) .frame(height: AppMetrics.Size.buttonHeight) .background(AppAccent.primary) @@ -94,23 +92,16 @@ private struct FeatureHelpCard: View { var body: some View { HStack(spacing: Design.Spacing.medium) { // Icon - Image(systemName: icon) - .font(.title2) - .foregroundStyle(AppAccent.primary) + SymbolIcon(icon, size: .row, color: AppAccent.primary) .frame(width: 44, height: 44) .background(AppAccent.primary.opacity(0.15)) .clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) // Text VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { - Text(title) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(title, .heading, emphasis: .custom(AppTextColors.primary)) - Text(description) - .font(.caption) - .foregroundStyle(AppTextColors.secondary) - .fixedSize(horizontal: false, vertical: true) + StyledLabel(description, .caption, emphasis: .custom(AppTextColors.secondary)) } Spacer() diff --git a/Andromida/App/Views/Rituals/Components/HabitPerformanceView.swift b/Andromida/App/Views/Rituals/Components/HabitPerformanceView.swift index b1081ff..35eccc1 100644 --- a/Andromida/App/Views/Rituals/Components/HabitPerformanceView.swift +++ b/Andromida/App/Views/Rituals/Components/HabitPerformanceView.swift @@ -29,9 +29,7 @@ struct HabitPerformanceView: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(String(localized: "Habit Performance")) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "Habit Performance"), .heading, emphasis: .custom(AppTextColors.primary)) LazyVGrid(columns: habitColumns, alignment: .leading, spacing: Design.Spacing.xSmall) { ForEach(sortedByRate, id: \.habit.id) { item in @@ -49,20 +47,14 @@ struct HabitPerformanceView: View { private func habitRow(_ habit: ArcHabit, rate: Double) -> some View { VStack(spacing: Design.Spacing.xSmall) { HStack(spacing: Design.Spacing.medium) { - Image(systemName: habit.symbolName) - .foregroundStyle(colorForRate(rate)) + SymbolIcon(habit.symbolName, size: .inline, color: colorForRate(rate)) .frame(width: AppMetrics.Size.iconMedium) - Text(habit.title) - .font(.subheadline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(habit.title, .subheading, emphasis: .custom(AppTextColors.primary)) Spacer() - Text("\(Int(rate * 100))%") - .font(.subheadline) - .bold() - .foregroundStyle(colorForRate(rate)) + StyledLabel("\(Int(rate * 100))%", .subheadingEmphasis, emphasis: .custom(colorForRate(rate))) } // Progress bar diff --git a/Andromida/App/Views/Rituals/Components/RitualMilestonesView.swift b/Andromida/App/Views/Rituals/Components/RitualMilestonesView.swift index 8656592..2f5846b 100644 --- a/Andromida/App/Views/Rituals/Components/RitualMilestonesView.swift +++ b/Andromida/App/Views/Rituals/Components/RitualMilestonesView.swift @@ -14,9 +14,7 @@ struct RitualMilestonesView: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(String(localized: "Milestones")) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "Milestones"), .heading, emphasis: .custom(AppTextColors.primary)) HStack(spacing: Design.Spacing.small) { ForEach(milestones) { milestone in @@ -37,20 +35,12 @@ struct RitualMilestonesView: View { .fill(milestone.isAchieved ? AppStatus.success.opacity(Design.Opacity.medium) : AppSurface.secondary) .frame(width: AppMetrics.Size.milestoneIcon, height: AppMetrics.Size.milestoneIcon) - Image(systemName: milestone.symbolName) - .font(.caption) - .foregroundStyle(milestone.isAchieved ? AppStatus.success : AppTextColors.tertiary) + SymbolIcon(milestone.symbolName, size: .badge, color: milestone.isAchieved ? AppStatus.success : AppTextColors.tertiary) } - Text("Day \(milestone.day)") - .font(.caption2) - .foregroundStyle(milestone.isAchieved ? AppTextColors.primary : AppTextColors.tertiary) + StyledLabel("Day \(milestone.day)", .caption2, emphasis: .custom(milestone.isAchieved ? AppTextColors.primary : AppTextColors.tertiary)) - Text(milestone.title) - .font(.caption2) - .foregroundStyle(milestone.isAchieved ? AppTextColors.secondary : AppTextColors.tertiary) - .lineLimit(1) - .minimumScaleFactor(0.8) + StyledLabel(milestone.title, .caption2, emphasis: .custom(milestone.isAchieved ? AppTextColors.secondary : AppTextColors.tertiary), lineLimit: 1) } .frame(maxWidth: .infinity) .accessibilityElement(children: .combine) diff --git a/Andromida/App/Views/Rituals/Components/RitualProgressStatsView.swift b/Andromida/App/Views/Rituals/Components/RitualProgressStatsView.swift index cd6109e..eaae7c0 100644 --- a/Andromida/App/Views/Rituals/Components/RitualProgressStatsView.swift +++ b/Andromida/App/Views/Rituals/Components/RitualProgressStatsView.swift @@ -40,9 +40,7 @@ struct RitualProgressStatsView: View { ProgressView(value: progress) .tint(AppAccent.primary) - Text("\(Int(progress * 100))% complete") - .font(.caption) - .foregroundStyle(AppTextColors.secondary) + StyledLabel("\(Int(progress * 100))% complete", .caption, emphasis: .custom(AppTextColors.secondary)) } } .padding(Design.Spacing.large) @@ -53,19 +51,12 @@ struct RitualProgressStatsView: View { private func statColumn(value: String, secondary: String?, label: String) -> some View { VStack(spacing: Design.Spacing.xSmall) { - Text(value) - .font(.title) - .fontWeight(.bold) - .foregroundStyle(AppTextColors.primary) + StyledLabel(value, .titleBold, emphasis: .custom(AppTextColors.primary)) // Always show secondary row for consistent height - Text(secondary ?? " ") - .font(.caption) - .foregroundStyle(secondary != nil ? AppTextColors.tertiary : .clear) + StyledLabel(secondary ?? " ", .caption, emphasis: .custom(secondary != nil ? AppTextColors.tertiary : .clear)) - Text(label) - .font(.caption2) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(label, .caption2, emphasis: .custom(AppTextColors.secondary)) } } } diff --git a/Andromida/App/Views/Rituals/RitualDetailView.swift b/Andromida/App/Views/Rituals/RitualDetailView.swift index f46fb98..594f217 100644 --- a/Andromida/App/Views/Rituals/RitualDetailView.swift +++ b/Andromida/App/Views/Rituals/RitualDetailView.swift @@ -108,9 +108,7 @@ struct RitualDetailView: View { if !ritual.notes.isEmpty { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { SectionHeaderView(title: String(localized: "Notes")) - Text(ritual.notes) - .font(.body) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(ritual.notes, .body, emphasis: .custom(AppTextColors.secondary)) } } @@ -258,11 +256,8 @@ struct RitualDetailView: View { // Summary card VStack(alignment: .leading, spacing: Design.Spacing.medium) { HStack { - Image(systemName: "clock.arrow.circlepath") - .foregroundStyle(AppTextColors.secondary) - Text(String(localized: "This ritual is not currently active")) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) + SymbolIcon("clock.arrow.circlepath", size: .inline, color: AppTextColors.secondary) + StyledLabel(String(localized: "This ritual is not currently active"), .subheading, emphasis: .custom(AppTextColors.secondary)) } if let lastArc = ritual.latestArc { @@ -271,9 +266,7 @@ struct RitualDetailView: View { let possibleCheckIns = habits.count * lastArc.durationDays let completionRate = possibleCheckIns > 0 ? Int(Double(totalCheckIns) / Double(possibleCheckIns) * 100) : 0 - Text(String(localized: "Last arc completed with \(completionRate)% habit completion over \(lastArc.durationDays) days.")) - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + StyledLabel(String(localized: "Last arc completed with \(completionRate)% habit completion over \(lastArc.durationDays) days."), .caption, emphasis: .custom(AppTextColors.tertiary)) } Button { @@ -319,21 +312,17 @@ struct RitualDetailView: View { HStack(spacing: Design.Spacing.medium) { // Current arc indicator if let arc = ritual.currentArc { - Text(String(localized: "Arc \(arc.arcNumber)")) - .font(.caption.bold()) + StyledLabel(String(localized: "Arc \(arc.arcNumber)"), .captionEmphasis, emphasis: .custom(AppAccent.primary)) .padding(.horizontal, Design.Spacing.small) .padding(.vertical, Design.Spacing.xSmall) .background(AppAccent.primary.opacity(0.2)) .clipShape(.capsule) - .foregroundStyle(AppAccent.primary) } else { - Text(String(localized: "No active arc")) - .font(.caption) + StyledLabel(String(localized: "No active arc"), .caption, emphasis: .custom(AppTextColors.tertiary)) .padding(.horizontal, Design.Spacing.small) .padding(.vertical, Design.Spacing.xSmall) .background(AppTextColors.tertiary.opacity(0.2)) .clipShape(.capsule) - .foregroundStyle(AppTextColors.tertiary) } // Time of day badge @@ -342,13 +331,11 @@ struct RitualDetailView: View { // Category badge (if set) if !ritual.category.isEmpty { let badgeColor = categoryStore.color(for: ritual.category) - Text(ritual.category) - .font(.caption) + StyledLabel(ritual.category, .caption, emphasis: .custom(badgeColor)) .padding(.horizontal, Design.Spacing.small) .padding(.vertical, Design.Spacing.xSmall) .background(badgeColor.opacity(0.15)) .clipShape(.capsule) - .foregroundStyle(badgeColor) } Spacer() @@ -357,15 +344,10 @@ struct RitualDetailView: View { private var timeOfDayBadge: some View { HStack(spacing: 2) { - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: ritual.timeOfDay.symbolName) - Text(ritual.timeOfDay.displayName) - } - Text(" : ") - Text(ritual.timeOfDay.timeRange) + IconLabel(ritual.timeOfDay.symbolName, ritual.timeOfDay.displayName, .caption2, emphasis: .custom(timeOfDayColor)) + StyledLabel(" : ", .caption2, emphasis: .custom(timeOfDayColor)) + StyledLabel(ritual.timeOfDay.timeRange, .caption2, emphasis: .custom(timeOfDayColor)) } - .font(.caption2) - .foregroundStyle(timeOfDayColor) .padding(.horizontal, Design.Spacing.small) .padding(.vertical, Design.Spacing.xSmall) .background(timeOfDayColor.opacity(0.15)) @@ -399,9 +381,7 @@ struct RitualDetailView: View { ) if completedArcs.isEmpty { - Text(String(localized: "No completed arcs yet.")) - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + StyledLabel(String(localized: "No completed arcs yet."), .caption, emphasis: .custom(AppTextColors.tertiary)) } else { LazyVGrid(columns: arcColumns, alignment: .leading, spacing: Design.Spacing.small) { ForEach(completedArcs) { arc in @@ -427,30 +407,20 @@ struct RitualDetailView: View { } label: { HStack { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { - Text(String(localized: "Arc \(arc.arcNumber)")) - .font(.subheadline.bold()) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "Arc \(arc.arcNumber)"), .subheadingEmphasis, emphasis: .custom(AppTextColors.primary)) - Text("\(dateFormatter.string(from: arc.startDate)) – \(dateFormatter.string(from: arc.endDate))") - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + StyledLabel("\(dateFormatter.string(from: arc.startDate)) – \(dateFormatter.string(from: arc.endDate))", .caption, emphasis: .custom(AppTextColors.tertiary)) } Spacer() VStack(alignment: .trailing, spacing: Design.Spacing.xSmall) { - Text("\(completionRate)%") - .font(.subheadline.bold()) - .foregroundStyle(completionRate >= 70 ? AppStatus.success : AppTextColors.secondary) + StyledLabel("\(completionRate)%", .subheadingEmphasis, emphasis: .custom(completionRate >= 70 ? AppStatus.success : AppTextColors.secondary)) - Text(String(localized: "\(arc.durationDays) days")) - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + StyledLabel(String(localized: "\(arc.durationDays) days"), .caption, emphasis: .custom(AppTextColors.tertiary)) } - Image(systemName: "chevron.right") - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + SymbolIcon.chevron(color: AppTextColors.tertiary) } .padding(Design.Spacing.medium) .background(AppSurface.card) @@ -466,44 +436,26 @@ struct RitualDetailView: View { HStack(spacing: Design.Spacing.medium) { // Days remaining if daysRemaining > 0 { - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: "hourglass") - .font(.caption) - Text(String(localized: "\(daysRemaining) days remaining")) - .font(.caption) - } - .foregroundStyle(AppTextColors.secondary) - .padding(.horizontal, Design.Spacing.medium) - .padding(.vertical, Design.Spacing.small) - .background(AppSurface.card) - .clipShape(.capsule) + IconLabel("hourglass", String(localized: "\(daysRemaining) days remaining"), .caption, emphasis: .custom(AppTextColors.secondary)) + .padding(.horizontal, Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) + .background(AppSurface.card) + .clipShape(.capsule) } else { - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: "checkmark.seal.fill") - .font(.caption) - Text(String(localized: "Arc complete!")) - .font(.caption) - } - .foregroundStyle(AppStatus.success) - .padding(.horizontal, Design.Spacing.medium) - .padding(.vertical, Design.Spacing.small) - .background(AppStatus.success.opacity(Design.Opacity.subtle)) - .clipShape(.capsule) + IconLabel("checkmark.seal.fill", String(localized: "Arc complete!"), .caption, emphasis: .custom(AppStatus.success)) + .padding(.horizontal, Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) + .background(AppStatus.success.opacity(Design.Opacity.subtle)) + .clipShape(.capsule) } // Ritual streak if ritualStreak > 0 { - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: "flame.fill") - .font(.caption) - Text(String(localized: "\(ritualStreak)-day streak")) - .font(.caption) - } - .foregroundStyle(AppStatus.success) - .padding(.horizontal, Design.Spacing.medium) - .padding(.vertical, Design.Spacing.small) - .background(AppStatus.success.opacity(Design.Opacity.subtle)) - .clipShape(.capsule) + IconLabel("flame.fill", String(localized: "\(ritualStreak)-day streak"), .caption, emphasis: .custom(AppStatus.success)) + .padding(.horizontal, Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) + .background(AppStatus.success.opacity(Design.Opacity.subtle)) + .clipShape(.capsule) } Spacer() @@ -511,15 +463,8 @@ struct RitualDetailView: View { // Arc comparison (only shown if there's a previous arc) if let comparison = arcComparisonInfo { - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: comparison.isAhead ? "arrow.up.right" : - comparison.isBehind ? "arrow.down.right" : "equal") - .font(.caption) - Text(comparison.text) - .font(.caption) - } - .foregroundStyle(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)) } } } diff --git a/Andromida/App/Views/Rituals/RitualsView.swift b/Andromida/App/Views/Rituals/RitualsView.swift index 7688224..67fa2db 100644 --- a/Andromida/App/Views/Rituals/RitualsView.swift +++ b/Andromida/App/Views/Rituals/RitualsView.swift @@ -140,12 +140,8 @@ struct RitualsView: View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { // Time of day header HStack(spacing: Design.Spacing.small) { - Image(systemName: group.timeOfDay.symbolName) - .foregroundStyle(AppAccent.primary) - Text(group.timeOfDay.displayName) - .font(.subheadline) - .bold() - .foregroundStyle(AppTextColors.secondary) + SymbolIcon(group.timeOfDay.symbolName, size: .inline, color: AppAccent.primary) + StyledLabel(group.timeOfDay.displayName, .subheadingEmphasis, emphasis: .custom(AppTextColors.secondary)) } .padding(.top, Design.Spacing.small) @@ -322,29 +318,18 @@ struct RitualsView: View { private func pastRitualCardView(for ritual: Ritual) -> some View { HStack(spacing: Design.Spacing.medium) { // Icon - Image(systemName: ritual.iconName) - .font(.title2) - .foregroundStyle(AppTextColors.secondary) + SymbolIcon(ritual.iconName, size: .row, color: AppTextColors.secondary) .frame(width: 40, height: 40) .background(AppSurface.secondary) .clipShape(.rect(cornerRadius: Design.CornerRadius.small)) VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { - Text(ritual.title) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(ritual.title, .heading, emphasis: .custom(AppTextColors.primary)) - Text(ritual.theme) - .font(.caption) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(ritual.theme, .caption, emphasis: .custom(AppTextColors.secondary)) if let lastArc = ritual.latestArc { - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: "calendar") - Text(formattedEndDate(lastArc.endDate)) - } - .font(.caption2) - .foregroundStyle(AppTextColors.tertiary) + IconLabel("calendar", formattedEndDate(lastArc.endDate), .caption2, emphasis: .custom(AppTextColors.tertiary)) } } @@ -352,18 +337,14 @@ struct RitualsView: View { // Arc count badge if ritual.completedArcCount > 0 { - Text("\(ritual.completedArcCount) arc\(ritual.completedArcCount == 1 ? "" : "s")") - .font(.caption2) - .foregroundStyle(AppTextColors.secondary) + StyledLabel("\(ritual.completedArcCount) arc\(ritual.completedArcCount == 1 ? "" : "s")", .caption2, emphasis: .custom(AppTextColors.secondary)) .padding(.horizontal, Design.Spacing.small) .padding(.vertical, Design.Spacing.xSmall) .background(AppSurface.secondary) .clipShape(.capsule) } - Image(systemName: "chevron.right") - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + SymbolIcon.chevron(color: AppTextColors.tertiary) } .padding(Design.Spacing.medium) .background(AppSurface.card) diff --git a/Andromida/App/Views/Rituals/Sheets/PresetLibrarySheet.swift b/Andromida/App/Views/Rituals/Sheets/PresetLibrarySheet.swift index dee6015..b87a17f 100644 --- a/Andromida/App/Views/Rituals/Sheets/PresetLibrarySheet.swift +++ b/Andromida/App/Views/Rituals/Sheets/PresetLibrarySheet.swift @@ -60,16 +60,11 @@ struct PresetLibrarySheet: View { selectedCategory = category } } label: { - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: category.symbolName) - Text(category.displayName) - } - .font(.subheadline) - .padding(.horizontal, Design.Spacing.medium) - .padding(.vertical, Design.Spacing.small) - .background(selectedCategory == category ? AppAccent.primary : AppSurface.card) - .foregroundStyle(selectedCategory == category ? .white : AppTextColors.primary) - .clipShape(.capsule) + IconLabel(category.symbolName, category.displayName, .subheading, emphasis: .custom(selectedCategory == category ? .white : AppTextColors.primary)) + .padding(.horizontal, Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) + .background(selectedCategory == category ? AppAccent.primary : AppSurface.card) + .clipShape(.capsule) } .buttonStyle(.plain) } @@ -90,50 +85,36 @@ struct PresetLibrarySheet: View { } label: { VStack(alignment: .leading, spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) { - Image(systemName: preset.iconName) - .font(.title2) - .foregroundStyle(AppAccent.primary) + SymbolIcon(preset.iconName, size: .row, color: AppAccent.primary) .frame(width: 40, height: 40) .background(AppAccent.primary.opacity(0.1)) .clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { HStack { - Text(preset.title) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(preset.title, .heading, emphasis: .custom(AppTextColors.primary)) if isAdded { - Image(systemName: "checkmark.circle.fill") - .foregroundStyle(AppStatus.success) - .font(.caption) + SymbolIcon("checkmark.circle.fill", size: .badge, color: AppStatus.success) } } - Text(preset.theme) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(preset.theme, .subheading, emphasis: .custom(AppTextColors.secondary)) } Spacer() VStack(alignment: .trailing, spacing: Design.Spacing.xSmall) { - Image(systemName: preset.timeOfDay.symbolName) - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + SymbolIcon(preset.timeOfDay.symbolName, size: .badge, color: AppTextColors.tertiary) - Text(String(localized: "\(preset.habits.count) habits")) - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + StyledLabel(String(localized: "\(preset.habits.count) habits"), .caption, emphasis: .custom(AppTextColors.tertiary)) } } // Habit preview HStack(spacing: Design.Spacing.small) { ForEach(preset.habits.prefix(4)) { habit in - Image(systemName: habit.symbolName) - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + SymbolIcon(habit.symbolName, size: .badge, color: AppTextColors.tertiary) } if preset.habits.count > 4 { @@ -218,13 +199,9 @@ struct PresetDetailSheet: View { private var descriptionSection: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(String(localized: "About")) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "About"), .heading, emphasis: .custom(AppTextColors.primary)) - Text(preset.notes) - .font(.body) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(preset.notes, .body, emphasis: .custom(AppTextColors.secondary)) } .padding(Design.Spacing.large) .frame(maxWidth: .infinity, alignment: .leading) @@ -234,20 +211,15 @@ struct PresetDetailSheet: View { private var habitsSection: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(String(localized: "Habits")) - .font(.headline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(String(localized: "Habits"), .heading, emphasis: .custom(AppTextColors.primary)) VStack(spacing: Design.Spacing.xSmall) { ForEach(preset.habits) { habit in HStack(spacing: Design.Spacing.medium) { - Image(systemName: habit.symbolName) - .foregroundStyle(AppAccent.primary) + SymbolIcon(habit.symbolName, size: .inline, color: AppAccent.primary) .frame(width: 24) - Text(habit.title) - .font(.subheadline) - .foregroundStyle(AppTextColors.primary) + StyledLabel(habit.title, .subheading, emphasis: .custom(AppTextColors.primary)) Spacer() } @@ -271,20 +243,15 @@ struct PresetDetailSheet: View { store.createRitualFromPreset(preset) hasBeenAdded = true } label: { - HStack { - if isAlreadyAdded || hasBeenAdded { - Image(systemName: "checkmark.circle.fill") - Text(String(localized: "Added to My Rituals")) - } else { - Image(systemName: "plus.circle.fill") - Text(String(localized: "Add to My Rituals")) - } - } - .font(.headline) + IconLabel( + isAlreadyAdded || hasBeenAdded ? "checkmark.circle.fill" : "plus.circle.fill", + isAlreadyAdded || hasBeenAdded ? String(localized: "Added to My Rituals") : String(localized: "Add to My Rituals"), + .heading, + emphasis: .custom(.white) + ) .frame(maxWidth: .infinity) .padding(Design.Spacing.medium) .background(isAlreadyAdded || hasBeenAdded ? AppStatus.success : AppAccent.primary) - .foregroundStyle(.white) .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) } .buttonStyle(.plain) diff --git a/Andromida/App/Views/Rituals/Sheets/RitualEditSheet.swift b/Andromida/App/Views/Rituals/Sheets/RitualEditSheet.swift index f64b00e..236b3ea 100644 --- a/Andromida/App/Views/Rituals/Sheets/RitualEditSheet.swift +++ b/Andromida/App/Views/Rituals/Sheets/RitualEditSheet.swift @@ -103,9 +103,7 @@ struct RitualEditSheet: View { Button { showingIconPicker = true } label: { - Image(systemName: iconName) - .font(.title) - .foregroundStyle(AppAccent.primary) + SymbolIcon(iconName, size: .card, color: AppAccent.primary) .frame(width: 44, height: 44) .background(AppSurface.card) .clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) @@ -113,10 +111,10 @@ struct RitualEditSheet: View { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { TextField(String(localized: "Ritual name"), text: $title) - .font(.headline) + .typography(.heading) TextField(String(localized: "Theme or tagline"), text: $theme) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(AppTextColors.secondary) } } @@ -124,9 +122,7 @@ struct RitualEditSheet: View { // Category selection - simple picker from CategoryStore VStack(alignment: .leading, spacing: Design.Spacing.medium) { - Text(String(localized: "Category")) - .font(.subheadline) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(String(localized: "Category"), .subheading, emphasis: .custom(AppTextColors.secondary)) // Horizontal scrollable category chips ScrollView(.horizontal, showsIndicators: false) { @@ -173,13 +169,7 @@ struct RitualEditSheet: View { } // Show the time range for the selected time of day - HStack(spacing: Design.Spacing.xSmall) { - Image(systemName: timeOfDay.symbolName) - .font(.caption) - Text(timeOfDay.timeRange) - .font(.caption) - } - .foregroundStyle(AppTextColors.tertiary) + IconLabel(timeOfDay.symbolName, timeOfDay.timeRange, .caption, emphasis: .custom(AppTextColors.tertiary)) } .listRowBackground(AppSurface.card) @@ -218,9 +208,7 @@ struct RitualEditSheet: View { HStack(spacing: Design.Spacing.xSmall) { Text(String(localized: "\(Int(durationDays)) days")) .foregroundStyle(AppTextColors.secondary) - Image(systemName: "pencil.circle") - .font(.caption) - .foregroundStyle(AppAccent.primary) + SymbolIcon("pencil.circle", size: .badge, color: AppAccent.primary) } } .buttonStyle(.plain) @@ -237,9 +225,7 @@ struct RitualEditSheet: View { Button { durationDays = Double(days) } label: { - Text("\(days)") - .font(.caption) - .foregroundStyle(Int(durationDays) == days ? AppAccent.primary : AppTextColors.tertiary) + StyledLabel("\(days)", .caption, emphasis: .custom(Int(durationDays) == days ? AppAccent.primary : AppTextColors.tertiary)) .padding(.horizontal, Design.Spacing.small) .padding(.vertical, Design.Spacing.xSmall) .background(Int(durationDays) == days ? AppAccent.primary.opacity(0.2) : AppSurface.secondary) @@ -255,8 +241,7 @@ struct RitualEditSheet: View { } header: { Text(String(localized: "Schedule")) } footer: { - Text(String(localized: "Tap the duration to enter a custom number of days (up to 365).")) - .font(.caption) + StyledLabel(String(localized: "Tap the duration to enter a custom number of days (up to 365)."), .caption, emphasis: .secondary) } } @@ -329,14 +314,11 @@ struct RitualEditSheet: View { HStack { Text(String(localized: "Habits")) Spacer() - Text(String(localized: "\(habits.count) habits")) - .font(.caption) - .foregroundStyle(AppTextColors.tertiary) + StyledLabel(String(localized: "\(habits.count) habits"), .caption, emphasis: .custom(AppTextColors.tertiary)) } } footer: { if habits.count > 1 { - Text(String(localized: "Drag the handle to reorder habits.")) - .font(.caption) + StyledLabel(String(localized: "Drag the handle to reorder habits."), .caption, emphasis: .secondary) } } } @@ -395,13 +377,11 @@ struct RitualEditSheet: View { .fill(color) .frame(width: 10, height: 10) } - Text(label) - .font(.subheadline) + StyledLabel(label, .subheading, emphasis: .custom(isSelected ? .white : AppTextColors.primary)) } .padding(.horizontal, Design.Spacing.medium) .padding(.vertical, Design.Spacing.small) .background(isSelected ? (color ?? AppAccent.primary) : AppSurface.secondary) - .foregroundStyle(isSelected ? .white : AppTextColors.primary) .clipShape(.capsule) } .buttonStyle(.plain) @@ -513,9 +493,7 @@ struct IconPickerSheet: View { LazyVStack(alignment: .leading, spacing: Design.Spacing.large) { ForEach(iconGroups, id: \.name) { group in VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(group.name) - .font(.caption) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(group.name, .caption, emphasis: .custom(AppTextColors.secondary)) .padding(.horizontal, Design.Spacing.small) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: Design.Spacing.small) { @@ -559,9 +537,7 @@ struct IconPickerSheet: View { selectedIcon = icon dismiss() } label: { - Image(systemName: icon) - .font(.title3) - .foregroundStyle(selectedIcon == icon ? AppAccent.primary : AppTextColors.secondary) + SymbolIcon(icon, size: .row, color: selectedIcon == icon ? AppAccent.primary : AppTextColors.secondary) .frame(width: 44, height: 44) .background(selectedIcon == icon ? AppAccent.primary.opacity(0.2) : AppSurface.card) .clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) @@ -621,9 +597,7 @@ struct HabitIconPickerSheet: View { LazyVStack(alignment: .leading, spacing: Design.Spacing.large) { ForEach(iconGroups, id: \.name) { group in VStack(alignment: .leading, spacing: Design.Spacing.small) { - Text(group.name) - .font(.caption) - .foregroundStyle(AppTextColors.secondary) + StyledLabel(group.name, .caption, emphasis: .custom(AppTextColors.secondary)) .padding(.horizontal, Design.Spacing.small) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: Design.Spacing.small) { @@ -675,9 +649,7 @@ struct HabitIconPickerSheet: View { selectedIcon = icon dismiss() } label: { - Image(systemName: icon) - .font(.title3) - .foregroundStyle(selectedIcon == icon ? AppAccent.primary : AppTextColors.secondary) + SymbolIcon(icon, size: .row, color: selectedIcon == icon ? AppAccent.primary : AppTextColors.secondary) .frame(width: 44, height: 44) .background(selectedIcon == icon ? AppAccent.primary.opacity(0.2) : AppSurface.card) .clipShape(.rect(cornerRadius: Design.CornerRadius.medium))