Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-02-01 17:29:49 -06:00
parent 1689e7cec2
commit f1cbd5f6a1
4 changed files with 94 additions and 79 deletions

View File

@ -7,12 +7,12 @@
<key>Andromida.xcscheme_^#shared#^_</key> <key>Andromida.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>2</integer> <integer>1</integer>
</dict> </dict>
<key>AndromidaWidgetExtension.xcscheme_^#shared#^_</key> <key>AndromidaWidgetExtension.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>1</integer> <integer>0</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View File

@ -1921,6 +1921,14 @@
"comment" : "Habit title for reading a physical book as part of a RitualPreset.", "comment" : "Habit title for reading a physical book as part of a RitualPreset.",
"isCommentAutoGenerated" : true "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" : { "Reflect" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -2221,6 +2229,10 @@
"comment" : "Title of a settings option that simulates a foreground refresh of the app.", "comment" : "Title of a settings option that simulates a foreground refresh of the app.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"Simulate Time of Day" : {
"comment" : "A label for the time of day picker.",
"isCommentAutoGenerated" : true
},
"Skip" : { "Skip" : {
"comment" : "Button label to skip onboarding.", "comment" : "Button label to skip onboarding.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,

View File

@ -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 // MARK: - Branding Colors Helper
extension Color { extension Color {
static let brandingPrimary = Color(red: 0.12, green: 0.09, blue: 0.08) static let brandingPrimary = Color(red: 0.12, green: 0.09, blue: 0.08)

View File

@ -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
}
}
}