From f1cbd5f6a1653c3d5c5fdcc801b4659550b84806 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sun, 1 Feb 2026 17:29:49 -0600 Subject: [PATCH] Signed-off-by: Matt Bruce --- .../xcschemes/xcschememanagement.plist | 4 +- .../App/Localization/Localizable.xcstrings | 12 +++ .../Views/AndromidaWidgetView.swift | 77 ------------------ .../Views/Components/LargeWidgetView.swift | 80 +++++++++++++++++++ 4 files changed, 94 insertions(+), 79 deletions(-) create mode 100644 AndromidaWidget/Views/Components/LargeWidgetView.swift diff --git a/Andromida.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist b/Andromida.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist index 16ae3f5..0d92d80 100644 --- a/Andromida.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Andromida.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ Andromida.xcscheme_^#shared#^_ orderHint - 2 + 1 AndromidaWidgetExtension.xcscheme_^#shared#^_ orderHint - 1 + 0 SuppressBuildableAutocreation diff --git a/Andromida/App/Localization/Localizable.xcstrings b/Andromida/App/Localization/Localizable.xcstrings index cb34c75..ac23562 100644 --- a/Andromida/App/Localization/Localizable.xcstrings +++ b/Andromida/App/Localization/Localizable.xcstrings @@ -1921,6 +1921,14 @@ "comment" : "Habit title for reading a physical book as part of a RitualPreset.", "isCommentAutoGenerated" : true }, + "Real" : { + "comment" : "The text for the \"Real\" option in the time of day picker.", + "isCommentAutoGenerated" : true + }, + "Real Time (%@)" : { + "comment" : "Text displayed in the debug picker to indicate whether it is showing the real time or a simulated time.", + "isCommentAutoGenerated" : true + }, "Reflect" : { "localizations" : { "en" : { @@ -2221,6 +2229,10 @@ "comment" : "Title of a settings option that simulates a foreground refresh of the app.", "isCommentAutoGenerated" : true }, + "Simulate Time of Day" : { + "comment" : "A label for the time of day picker.", + "isCommentAutoGenerated" : true + }, "Skip" : { "comment" : "Button label to skip onboarding.", "isCommentAutoGenerated" : true, diff --git a/AndromidaWidget/Views/AndromidaWidgetView.swift b/AndromidaWidget/Views/AndromidaWidgetView.swift index f34f568..fe1641f 100644 --- a/AndromidaWidget/Views/AndromidaWidgetView.swift +++ b/AndromidaWidget/Views/AndromidaWidgetView.swift @@ -24,83 +24,6 @@ struct AndromidaWidgetView: View { } } -struct LargeWidgetView: View { - let entry: WidgetEntry - - var body: some View { - VStack(alignment: .leading, spacing: Design.Spacing.large) { - HStack { - VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { - Text(String(localized: "Today's Progress")) - .styled(.heading, emphasis: .custom(AppTextColors.primary)) - Text("\(entry.currentStreak) day streak") - .styled(.subheading, emphasis: .custom(AppAccent.primary)) - } - Spacer() - ZStack { - Circle() - .stroke(AppTextColors.primary.opacity(0.1), lineWidth: 6) - Circle() - .trim(from: 0, to: entry.completionRate) - .stroke(AppAccent.primary, style: StrokeStyle(lineWidth: 6, lineCap: .round)) - .rotationEffect(.degrees(-90)) - Text("\(Int(entry.completionRate * 100))%") - .styled(.captionEmphasis, emphasis: .custom(AppTextColors.primary)) - } - .frame(width: 50, height: 50) - } - .padding(.top, Design.Spacing.small) - - Divider() - .background(AppTextColors.primary.opacity(0.2)) - - if entry.nextHabits.isEmpty { - WidgetEmptyStateView( - iconSize: .section, - title: String(localized: "No rituals scheduled for \(entry.currentTimeOfDay.lowercased())."), - subtitle: entry.currentTimeOfDay, - symbolName: entry.currentTimeOfDaySymbol, - timeRange: entry.currentTimeOfDayRange, - nextRitual: entry.nextRitualInfo, - isCompact: false - ) - } else { - Text(String(localized: "Habits")) - .styled(.captionEmphasis, emphasis: .custom(AppTextColors.secondary)) - - VStack(spacing: Design.Spacing.medium) { - ForEach(entry.nextHabits) { habit in - HStack(spacing: Design.Spacing.medium) { - Image(systemName: habit.symbolName) - .foregroundColor(AppAccent.primary) - .font(.system(size: 18)) - .frame(width: 24) - - VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { - Text(habit.title) - .styled(.subheading, emphasis: .custom(AppTextColors.primary)) - Text(habit.ritualTitle) - .styled(.caption, emphasis: .custom(AppTextColors.tertiary)) - } - Spacer() - - Image(systemName: habit.isCompleted ? "checkmark.circle.fill" : "circle") - .foregroundColor(habit.isCompleted ? .green : AppTextColors.primary.opacity(0.2)) - .font(.system(size: 20)) - } - } - } - } - - Spacer() - } - .padding(Design.Spacing.large) - .containerBackground(for: .widget) { - AppSurface.primary - } - } -} - // MARK: - Branding Colors Helper extension Color { static let brandingPrimary = Color(red: 0.12, green: 0.09, blue: 0.08) diff --git a/AndromidaWidget/Views/Components/LargeWidgetView.swift b/AndromidaWidget/Views/Components/LargeWidgetView.swift new file mode 100644 index 0000000..fc4db36 --- /dev/null +++ b/AndromidaWidget/Views/Components/LargeWidgetView.swift @@ -0,0 +1,80 @@ +import SwiftUI +import WidgetKit +import Bedrock + +struct LargeWidgetView: View { + let entry: WidgetEntry + + var body: some View { + VStack(alignment: .leading, spacing: Design.Spacing.large) { + HStack { + VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { + Text(String(localized: "Today's Progress")) + .styled(.heading, emphasis: .custom(AppTextColors.primary)) + Text("\(entry.currentStreak) day streak") + .styled(.subheading, emphasis: .custom(AppAccent.primary)) + } + Spacer() + ZStack { + Circle() + .stroke(AppTextColors.primary.opacity(0.1), lineWidth: 6) + Circle() + .trim(from: 0, to: entry.completionRate) + .stroke(AppAccent.primary, style: StrokeStyle(lineWidth: 6, lineCap: .round)) + .rotationEffect(.degrees(-90)) + Text("\(Int(entry.completionRate * 100))%") + .styled(.captionEmphasis, emphasis: .custom(AppTextColors.primary)) + } + .frame(width: 50, height: 50) + } + .padding(.top, Design.Spacing.small) + + Divider() + .background(AppTextColors.primary.opacity(0.2)) + + if entry.nextHabits.isEmpty { + WidgetEmptyStateView( + iconSize: .section, + title: String(localized: "No rituals scheduled for \(entry.currentTimeOfDay.lowercased())."), + subtitle: entry.currentTimeOfDay, + symbolName: entry.currentTimeOfDaySymbol, + timeRange: entry.currentTimeOfDayRange, + nextRitual: entry.nextRitualInfo, + isCompact: false + ) + } else { + Text(String(localized: "Habits")) + .styled(.captionEmphasis, emphasis: .custom(AppTextColors.secondary)) + + VStack(spacing: Design.Spacing.medium) { + ForEach(entry.nextHabits) { habit in + HStack(spacing: Design.Spacing.medium) { + Image(systemName: habit.symbolName) + .foregroundColor(AppAccent.primary) + .font(.system(size: 18)) + .frame(width: 24) + + VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { + Text(habit.title) + .styled(.subheading, emphasis: .custom(AppTextColors.primary)) + Text(habit.ritualTitle) + .styled(.caption, emphasis: .custom(AppTextColors.tertiary)) + } + Spacer() + + Image(systemName: habit.isCompleted ? "checkmark.circle.fill" : "circle") + .foregroundColor(habit.isCompleted ? .green : AppTextColors.primary.opacity(0.2)) + .font(.system(size: 20)) + } + } + } + } + + Spacer() + } + .padding(Design.Spacing.large) + .containerBackground(for: .widget) { + AppSurface.primary + } + } +}