Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
baedb98b2b
commit
08fef9ffe3
@ -11,6 +11,9 @@
|
|||||||
"-%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.",
|
"comment" : "A subline of text showing the start and end dates of an arc.",
|
||||||
@ -319,10 +322,32 @@
|
|||||||
"comment" : "A placeholder text for a text field that allows users to input the name of a new habit.",
|
"comment" : "A placeholder text for a text field that allows users to input the name of a new habit.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Add Andromida to your Home Screen for quick check-ins." : {
|
||||||
|
"comment" : "Description on the widget discovery card in onboarding.",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Add Andromida to your Home Screen for quick check-ins."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Add notes or reminders..." : {
|
"Add notes or reminders..." : {
|
||||||
"comment" : "A placeholder text for a text field where the user can add notes or reminders.",
|
"comment" : "A placeholder text for a text field where the user can add notes or reminders.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Add the widget" : {
|
||||||
|
"comment" : "Title for the widget setup sheet in onboarding.",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Add the widget"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Add to My Rituals" : {
|
"Add to My Rituals" : {
|
||||||
"comment" : "A button label that says \"Add to My Rituals\".",
|
"comment" : "A button label that says \"Add to My Rituals\".",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -1271,6 +1296,17 @@
|
|||||||
"comment" : "Title of the history view.",
|
"comment" : "Title of the history view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"How to add" : {
|
||||||
|
"comment" : "CTA button label to show widget setup steps.",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "How to add"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Hydrate" : {
|
"Hydrate" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -1383,6 +1419,17 @@
|
|||||||
"comment" : "Habit title for keeping the bedroom cool at night.",
|
"comment" : "Habit title for keeping the bedroom cool at night.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Keep your rituals visible at a glance." : {
|
||||||
|
"comment" : "Subtitle for the widget setup sheet in onboarding.",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Keep your rituals visible at a glance."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Last arc completed with %lld%% habit completion over %lld days." : {
|
"Last arc completed with %lld%% habit completion over %lld days." : {
|
||||||
"comment" : "A caption that provides details about the last arc of a ritual, including the number of days it lasted and the percentage of habit completions.",
|
"comment" : "A caption that provides details about the last arc of a ritual, including the number of days it lasted and the percentage of habit completions.",
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
@ -2103,6 +2150,17 @@
|
|||||||
"comment" : "A label displayed above the ritual's scheduling information.",
|
"comment" : "A label displayed above the ritual's scheduling information.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Search for Andromida and pick a size." : {
|
||||||
|
"comment" : "Widget setup step: search for the app and choose size.",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Search for Andromida and pick a size."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Search icons" : {
|
"Search icons" : {
|
||||||
"comment" : "A placeholder text for a search bar in an icon picker sheet.",
|
"comment" : "A placeholder text for a search bar in an icon picker sheet.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -2158,6 +2216,10 @@
|
|||||||
},
|
},
|
||||||
"Shows rituals for the current time of day. Check in here daily." : {
|
"Shows rituals for the current time of day. Check in here daily." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Simulate Foreground Refresh" : {
|
||||||
|
"comment" : "Title of a settings option that simulates a foreground refresh of the app.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Skip" : {
|
"Skip" : {
|
||||||
"comment" : "Button label to skip onboarding.",
|
"comment" : "Button label to skip onboarding.",
|
||||||
@ -2357,6 +2419,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Tap Edit, then Add Widget." : {
|
||||||
|
"comment" : "Widget setup step: tap Edit and Add Widget.",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Tap Edit, then Add Widget."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Tap for details" : {
|
"Tap for details" : {
|
||||||
"comment" : "A hint that appears when a user taps on an element to learn more about it.",
|
"comment" : "A hint that appears when a user taps on an element to learn more about it.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -2503,6 +2576,17 @@
|
|||||||
"Total Check-ins" : {
|
"Total Check-ins" : {
|
||||||
"comment" : "Title for an insight card showing the total number of habits completed all-time."
|
"comment" : "Title for an insight card showing the total number of habits completed all-time."
|
||||||
},
|
},
|
||||||
|
"Touch and hold your Home Screen." : {
|
||||||
|
"comment" : "Widget setup step: long-press Home Screen.",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Touch and hold your Home Screen."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Track your streaks, progress, and trends over time." : {
|
"Track your streaks, progress, and trends over time." : {
|
||||||
"comment" : "Description of a feature card in the \"WhatsNextStepView\" that explains how to use the app's insights feature.",
|
"comment" : "Description of a feature card in the \"WhatsNextStepView\" that explains how to use the app's insights feature.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -2618,6 +2702,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Widgets" : {
|
||||||
|
"comment" : "Title for the widgets discovery card in onboarding.",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Widgets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Wind down with a reminder when it's time for your evening ritual" : {
|
"Wind down with a reminder when it's time for your evening ritual" : {
|
||||||
"comment" : "Description for notification permission screen when user selected evening rituals."
|
"comment" : "Description for notification permission screen when user selected evening rituals."
|
||||||
},
|
},
|
||||||
@ -2734,94 +2829,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Widgets" : {
|
|
||||||
"comment" : "Title for the widgets discovery card in onboarding.",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "Widgets"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Add Andromida to your Home Screen for quick check-ins." : {
|
|
||||||
"comment" : "Description on the widget discovery card in onboarding.",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "Add Andromida to your Home Screen for quick check-ins."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"How to add" : {
|
|
||||||
"comment" : "CTA button label to show widget setup steps.",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "How to add"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Add the widget" : {
|
|
||||||
"comment" : "Title for the widget setup sheet in onboarding.",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "Add the widget"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Keep your rituals visible at a glance." : {
|
|
||||||
"comment" : "Subtitle for the widget setup sheet in onboarding.",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "Keep your rituals visible at a glance."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Touch and hold your Home Screen." : {
|
|
||||||
"comment" : "Widget setup step: long-press Home Screen.",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "Touch and hold your Home Screen."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Tap Edit, then Add Widget." : {
|
|
||||||
"comment" : "Widget setup step: tap Edit and Add Widget.",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "Tap Edit, then Add Widget."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Search for Andromida and pick a size." : {
|
|
||||||
"comment" : "Widget setup step: search for the app and choose size.",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "Search for Andromida and pick a size."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version" : "1.1"
|
"version" : "1.1"
|
||||||
|
|||||||
@ -26,6 +26,7 @@ final class RitualStore: RitualStoreProviding {
|
|||||||
private var pendingReminderTask: Task<Void, Never>?
|
private var pendingReminderTask: Task<Void, Never>?
|
||||||
private var insightCardsNeedRefresh = true
|
private var insightCardsNeedRefresh = true
|
||||||
private var cachedInsightCards: [InsightCard] = []
|
private var cachedInsightCards: [InsightCard] = []
|
||||||
|
private var lastRefreshDate: Date?
|
||||||
|
|
||||||
/// Reminder scheduler for time-slot based notifications
|
/// Reminder scheduler for time-slot based notifications
|
||||||
let reminderScheduler = ReminderScheduler()
|
let reminderScheduler = ReminderScheduler()
|
||||||
@ -105,6 +106,16 @@ final class RitualStore: RitualStoreProviding {
|
|||||||
reloadRituals()
|
reloadRituals()
|
||||||
checkForCompletedArcs()
|
checkForCompletedArcs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Refreshes rituals if the last refresh was beyond the minimum interval.
|
||||||
|
func refreshIfNeeded(minimumInterval: TimeInterval = 5) {
|
||||||
|
let now = Date()
|
||||||
|
if let lastRefreshDate, now.timeIntervalSince(lastRefreshDate) < minimumInterval {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastRefreshDate = now
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
func ritualProgress(for ritual: Ritual) -> Double {
|
func ritualProgress(for ritual: Ritual) -> Double {
|
||||||
let habits = ritual.habits
|
let habits = ritual.habits
|
||||||
|
|||||||
@ -150,6 +150,7 @@ struct HistoryView: View {
|
|||||||
refreshToken = UUID()
|
refreshToken = UUID()
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
store.refreshIfNeeded()
|
||||||
refreshProgressCache()
|
refreshProgressCache()
|
||||||
}
|
}
|
||||||
.sheet(item: $selectedDateItem) { item in
|
.sheet(item: $selectedDateItem) { item in
|
||||||
|
|||||||
@ -80,6 +80,7 @@ struct InsightsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
store.refreshIfNeeded()
|
||||||
cardOrder = store.insightCardOrder
|
cardOrder = store.insightCardOrder
|
||||||
Task {
|
Task {
|
||||||
await Task.yield()
|
await Task.yield()
|
||||||
|
|||||||
@ -125,6 +125,9 @@ struct RitualsView: View {
|
|||||||
.onChange(of: store.rituals) { _, _ in
|
.onChange(of: store.rituals) { _, _ in
|
||||||
refreshToken = UUID()
|
refreshToken = UUID()
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
store.refreshIfNeeded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Current Tab Content
|
// MARK: - Current Tab Content
|
||||||
@ -141,7 +144,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)
|
||||||
Text(group.timeOfDay.displayName).styled(.subheadingEmphasis, emphasis: .secondary)
|
Text(group.timeOfDay.displayNameWithRange).styled(.subheadingEmphasis, emphasis: .secondary)
|
||||||
}
|
}
|
||||||
.padding(.top, Design.Spacing.small)
|
.padding(.top, Design.Spacing.small)
|
||||||
|
|
||||||
|
|||||||
@ -106,22 +106,29 @@ struct PresetLibrarySheet: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(alignment: .trailing, spacing: Design.Spacing.xSmall) {
|
VStack(alignment: .trailing, spacing: Design.Spacing.xSmall) {
|
||||||
SymbolIcon(preset.timeOfDay.symbolName, size: .badge, color: AppTextColors.tertiary)
|
|
||||||
|
|
||||||
Text(String(localized: "\(preset.habits.count) habits")).styled(.caption, emphasis: .tertiary)
|
Text(String(localized: "\(preset.habits.count) habits")).styled(.caption, emphasis: .tertiary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Habit preview
|
// Habit preview and Time of Day
|
||||||
HStack(spacing: Design.Spacing.small) {
|
HStack(alignment: .center, spacing: Design.Spacing.small) {
|
||||||
ForEach(preset.habits.prefix(4)) { habit in
|
// Habit icons
|
||||||
SymbolIcon(habit.symbolName, size: .badge, color: AppTextColors.tertiary)
|
HStack(spacing: Design.Spacing.small) {
|
||||||
|
ForEach(preset.habits.prefix(4)) { habit in
|
||||||
|
SymbolIcon(habit.symbolName, size: .badge, color: AppTextColors.tertiary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if preset.habits.count > 4 {
|
||||||
|
Text("+\(preset.habits.count - 4)").styled(.caption2, emphasis: .tertiary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if preset.habits.count > 4 {
|
Spacer()
|
||||||
Text("+\(preset.habits.count - 4)").styled(.caption2, emphasis: .tertiary)
|
|
||||||
}
|
// Time of Day pill
|
||||||
|
timeOfDayPill(for: preset.timeOfDay)
|
||||||
}
|
}
|
||||||
|
.padding(.top, Design.Spacing.small)
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
.background(AppSurface.card)
|
.background(AppSurface.card)
|
||||||
@ -187,7 +194,7 @@ struct PresetDetailSheet: View {
|
|||||||
.foregroundStyle(AppTextColors.secondary)
|
.foregroundStyle(AppTextColors.secondary)
|
||||||
|
|
||||||
HStack(spacing: Design.Spacing.large) {
|
HStack(spacing: Design.Spacing.large) {
|
||||||
Label(preset.timeOfDay.displayName, systemImage: preset.timeOfDay.symbolName)
|
Label(preset.timeOfDay.displayNameWithRange, systemImage: preset.timeOfDay.symbolName)
|
||||||
Label(String(localized: "\(preset.durationDays) days"), systemImage: "calendar")
|
Label(String(localized: "\(preset.durationDays) days"), systemImage: "calendar")
|
||||||
}
|
}
|
||||||
.typography(.caption)
|
.typography(.caption)
|
||||||
@ -260,3 +267,40 @@ struct PresetDetailSheet: View {
|
|||||||
#Preview {
|
#Preview {
|
||||||
PresetLibrarySheet(store: RitualStore.preview)
|
PresetLibrarySheet(store: RitualStore.preview)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension PresetLibrarySheet {
|
||||||
|
private func timeOfDayPill(for timeOfDay: TimeOfDay) -> some View {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Image(systemName: timeOfDay.symbolName)
|
||||||
|
.font(.system(size: 10))
|
||||||
|
Text(timeOfDay.displayName)
|
||||||
|
.font(.system(size: 10, weight: .bold))
|
||||||
|
Text(":")
|
||||||
|
.font(.system(size: 10))
|
||||||
|
Text(timeOfDay.timeRange)
|
||||||
|
.font(.system(size: 10))
|
||||||
|
}
|
||||||
|
.foregroundStyle(timeOfDayColor(for: timeOfDay))
|
||||||
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
|
.padding(.vertical, Design.Spacing.xxxSmall)
|
||||||
|
.background(timeOfDayColor(for: timeOfDay).opacity(0.15))
|
||||||
|
.clipShape(.capsule)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timeOfDayColor(for timeOfDay: TimeOfDay) -> Color {
|
||||||
|
switch timeOfDay {
|
||||||
|
case .morning:
|
||||||
|
return Color.orange
|
||||||
|
case .midday:
|
||||||
|
return Color.yellow
|
||||||
|
case .afternoon:
|
||||||
|
return Color.orange.opacity(0.8)
|
||||||
|
case .evening:
|
||||||
|
return Color.purple
|
||||||
|
case .night:
|
||||||
|
return Color.indigo
|
||||||
|
case .anytime:
|
||||||
|
return AppTextColors.secondary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -161,16 +161,15 @@ struct RitualEditSheet: View {
|
|||||||
private var scheduleSection: some View {
|
private var scheduleSection: some View {
|
||||||
Section {
|
Section {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
Picker(String(localized: "Time of Day"), selection: $timeOfDay) {
|
Picker(selection: $timeOfDay) {
|
||||||
ForEach(TimeOfDay.allCases, id: \.self) { time in
|
ForEach(TimeOfDay.allCases, id: \.self) { time in
|
||||||
Label(time.displayName, systemImage: time.symbolName)
|
Text(time.displayNameWithRange)
|
||||||
.tag(time)
|
.tag(time)
|
||||||
}
|
}
|
||||||
|
} label: {
|
||||||
|
Text(String(localized: "Time of Day"))
|
||||||
}
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
// Show the time range for the selected time of day
|
|
||||||
Label(timeOfDay.timeRange, systemImage: timeOfDay.symbolName)
|
|
||||||
.styled(.caption, emphasis: .tertiary)
|
|
||||||
}
|
}
|
||||||
.listRowBackground(AppSurface.card)
|
.listRowBackground(AppSurface.card)
|
||||||
|
|
||||||
@ -461,15 +460,18 @@ struct IconPickerSheet: View {
|
|||||||
("Wellness", ["heart.fill", "heart.circle.fill", "drop.fill", "leaf.fill", "flame.fill", "bolt.fill", "pill.fill", "cross.fill", "stethoscope.circle.fill", "bandage.fill", "lungs.fill", "ear.fill"]),
|
("Wellness", ["heart.fill", "heart.circle.fill", "drop.fill", "leaf.fill", "flame.fill", "bolt.fill", "pill.fill", "cross.fill", "stethoscope.circle.fill", "bandage.fill", "lungs.fill", "ear.fill"]),
|
||||||
("Time", ["sunrise.fill", "sun.max.fill", "sunset.fill", "moon.stars.fill", "moon.fill", "moon.zzz.fill", "clock.fill", "hourglass", "timer", "alarm.fill", "calendar", "calendar.badge.clock"]),
|
("Time", ["sunrise.fill", "sun.max.fill", "sunset.fill", "moon.stars.fill", "moon.fill", "moon.zzz.fill", "clock.fill", "hourglass", "timer", "alarm.fill", "calendar", "calendar.badge.clock"]),
|
||||||
("Activity", ["figure.walk", "figure.run", "figure.mind.and.body", "figure.flexibility", "figure.cooldown", "figure.hiking", "figure.yoga", "figure.strengthtraining.traditional", "figure.dance", "figure.pool.swim", "dumbbell.fill", "sportscourt.fill", "bicycle"]),
|
("Activity", ["figure.walk", "figure.run", "figure.mind.and.body", "figure.flexibility", "figure.cooldown", "figure.hiking", "figure.yoga", "figure.strengthtraining.traditional", "figure.dance", "figure.pool.swim", "dumbbell.fill", "sportscourt.fill", "bicycle"]),
|
||||||
("Mind", ["brain", "brain.head.profile", "sparkles", "star.fill", "lightbulb.fill", "eye.fill", "wand.and.stars", "hands.and.sparkles.fill", "person.and.background.dotted"]),
|
("Mind", ["brain", "brain.head.profile", "sparkles", "star.fill", "lightbulb.fill", "eye.fill", "wand.and.stars", "hands.and.sparkles.fill", "person.and.background.dotted", "infinity"]),
|
||||||
|
("Education", ["graduationcap.fill", "book.pages.fill", "abc", "pencil.and.ruler.fill", "backpack.fill"]),
|
||||||
("Objects", ["book.fill", "book.closed.fill", "books.vertical.fill", "pencil.and.list.clipboard", "cup.and.saucer.fill", "mug.fill", "bed.double.fill", "tshirt.fill", "fork.knife", "gift.fill", "key.fill"]),
|
("Objects", ["book.fill", "book.closed.fill", "books.vertical.fill", "pencil.and.list.clipboard", "cup.and.saucer.fill", "mug.fill", "bed.double.fill", "tshirt.fill", "fork.knife", "gift.fill", "key.fill"]),
|
||||||
("Nature", ["tree.fill", "wind", "cloud.fill", "snowflake", "sun.horizon.fill", "moon.fill", "mountain.2.fill", "water.waves", "leaf.arrow.circlepath", "flower.fill"]),
|
("Nature", ["tree.fill", "wind", "cloud.fill", "snowflake", "sun.horizon.fill", "moon.fill", "mountain.2.fill", "water.waves", "leaf.arrow.circlepath", "flower.fill", "sprout.fill"]),
|
||||||
("Home", ["house.fill", "bed.double.fill", "bathtub.fill", "shower.fill", "lamp.desk.fill", "chair.fill", "sofa.fill", "washer.fill"]),
|
("Home", ["house.fill", "bed.double.fill", "bathtub.fill", "shower.fill", "lamp.desk.fill", "chair.fill", "sofa.fill", "washer.fill", "hammer.fill", "wrench.adjustable.fill", "screwdriver.fill"]),
|
||||||
("Work", ["briefcase.fill", "folder.fill", "doc.text.fill", "list.bullet.clipboard.fill", "checklist", "tray.full.fill", "chart.bar.fill", "gauge.with.dots.needle.bottom.50percent"]),
|
("Work", ["briefcase.fill", "folder.fill", "doc.text.fill", "list.bullet.clipboard.fill", "checklist", "tray.full.fill", "chart.bar.fill", "gauge.with.dots.needle.bottom.50percent"]),
|
||||||
("Social", ["person.fill", "person.2.fill", "person.wave.2.fill", "bubble.left.fill", "phone.fill", "envelope.fill", "hand.wave.fill", "face.smiling.fill"]),
|
("Social", ["person.fill", "person.2.fill", "person.wave.2.fill", "bubble.left.fill", "phone.fill", "envelope.fill", "hand.wave.fill", "face.smiling.fill"]),
|
||||||
|
("Hobbies", ["camera.shutter.button.fill", "gamecontroller.fill", "puzzlepiece.fill", "die.face.5.fill", "binoculars.fill", "theatermasks.fill", "paintpalette.fill"]),
|
||||||
("Actions", ["checkmark.circle.fill", "target", "scope", "hand.thumbsup.fill", "hand.raised.fill", "bell.fill", "megaphone.fill", "flag.fill", "pin.fill"]),
|
("Actions", ["checkmark.circle.fill", "target", "scope", "hand.thumbsup.fill", "hand.raised.fill", "bell.fill", "megaphone.fill", "flag.fill", "pin.fill"]),
|
||||||
("Finance", ["banknote.fill", "dollarsign.circle.fill", "creditcard.fill", "cart.fill", "bag.fill"]),
|
("Finance", ["banknote.fill", "dollarsign.circle.fill", "creditcard.fill", "cart.fill", "bag.fill"]),
|
||||||
("Travel", ["airplane", "car.fill", "tram.fill", "ferry.fill", "mappin.circle.fill", "globe.americas.fill"]),
|
("Travel", ["airplane", "car.fill", "tram.fill", "ferry.fill", "mappin.circle.fill", "globe.americas.fill"]),
|
||||||
|
("Animals", ["dog.fill", "cat.fill", "pawprint.fill", "bird.fill", "tortoise.fill", "lizard.fill", "fish.fill", "hare.fill", "ladybug.fill"]),
|
||||||
("Art", ["paintpalette.fill", "paintbrush.fill", "pencil.tip.crop.circle", "scissors", "theatermasks.fill", "music.note", "guitars.fill"]),
|
("Art", ["paintpalette.fill", "paintbrush.fill", "pencil.tip.crop.circle", "scissors", "theatermasks.fill", "music.note", "guitars.fill"]),
|
||||||
("Tech", ["iphone", "desktopcomputer", "keyboard.fill", "headphones", "wifi", "antenna.radiowaves.left.and.right", "camera.fill", "photo.fill"]),
|
("Tech", ["iphone", "desktopcomputer", "keyboard.fill", "headphones", "wifi", "antenna.radiowaves.left.and.right", "camera.fill", "photo.fill"]),
|
||||||
("Arrows", ["arrow.up.circle.fill", "arrow.down.circle.fill", "arrow.counterclockwise.circle.fill", "arrow.2.circlepath", "arrow.triangle.2.circlepath"])
|
("Arrows", ["arrow.up.circle.fill", "arrow.down.circle.fill", "arrow.counterclockwise.circle.fill", "arrow.2.circlepath", "arrow.triangle.2.circlepath"])
|
||||||
@ -557,17 +559,20 @@ struct HabitIconPickerSheet: View {
|
|||||||
("Common", ["circle.fill", "star.fill", "heart.fill", "bolt.fill", "leaf.fill", "flame.fill", "drop.fill", "sparkles", "checkmark.circle.fill", "target"]),
|
("Common", ["circle.fill", "star.fill", "heart.fill", "bolt.fill", "leaf.fill", "flame.fill", "drop.fill", "sparkles", "checkmark.circle.fill", "target"]),
|
||||||
("Wellness", ["heart.fill", "heart.circle.fill", "cross.fill", "pill.fill", "stethoscope.circle.fill", "bandage.fill", "medical.thermometer.fill", "lungs.fill", "waveform.path.ecg", "face.smiling.fill"]),
|
("Wellness", ["heart.fill", "heart.circle.fill", "cross.fill", "pill.fill", "stethoscope.circle.fill", "bandage.fill", "medical.thermometer.fill", "lungs.fill", "waveform.path.ecg", "face.smiling.fill"]),
|
||||||
("Fitness", ["figure.walk", "figure.run", "figure.mind.and.body", "figure.flexibility", "figure.cooldown", "figure.hiking", "figure.yoga", "figure.strengthtraining.traditional", "figure.dance", "figure.pool.swim", "figure.outdoor.cycle", "dumbbell.fill", "sportscourt.fill", "bicycle", "tennis.racket", "basketball.fill", "soccerball"]),
|
("Fitness", ["figure.walk", "figure.run", "figure.mind.and.body", "figure.flexibility", "figure.cooldown", "figure.hiking", "figure.yoga", "figure.strengthtraining.traditional", "figure.dance", "figure.pool.swim", "figure.outdoor.cycle", "dumbbell.fill", "sportscourt.fill", "bicycle", "tennis.racket", "basketball.fill", "soccerball"]),
|
||||||
("Food & Drink", ["drop.fill", "cup.and.saucer.fill", "mug.fill", "fork.knife", "carrot.fill", "fish.fill", "leaf.fill", "takeoutbag.and.cup.and.straw.fill", "birthday.cake.fill", "wineglass.fill"]),
|
("Food & Drink", ["drop.fill", "cup.and.saucer.fill", "mug.fill", "fork.knife", "carrot.fill", "fish.fill", "leaf.fill", "takeoutbag.and.cup.and.straw.fill", "birthday.cake.fill", "wineglass.fill", "apple.logo"]),
|
||||||
("Mind", ["brain", "brain.head.profile", "lightbulb.fill", "eye.fill", "ear.fill", "sparkles", "wand.and.stars", "hands.and.sparkles.fill", "person.and.background.dotted", "moon.zzz.fill"]),
|
("Mind", ["brain", "brain.head.profile", "lightbulb.fill", "eye.fill", "ear.fill", "sparkles", "wand.and.stars", "hands.and.sparkles.fill", "person.and.background.dotted", "moon.zzz.fill", "infinity"]),
|
||||||
("Reading & Writing", ["book.fill", "book.closed.fill", "books.vertical.fill", "text.book.closed.fill", "pencil", "pencil.and.list.clipboard", "note.text", "doc.text.fill", "newspaper.fill", "bookmark.fill"]),
|
("Education", ["graduationcap.fill", "book.pages.fill", "abc", "pencil.and.ruler.fill", "backpack.fill"]),
|
||||||
|
("Reading & Writing", ["book.fill", "book.closed.fill", "books.vertical.fill", "text.book.closed.fill", "pencil", "pencil.and.list.clipboard", "note.text", "doc.text.fill", "newspaper.fill", "bookmark.fill", "square.and.pencil"]),
|
||||||
("Time", ["clock.fill", "alarm.fill", "timer", "hourglass", "sunrise.fill", "sunset.fill", "moon.fill", "moon.stars.fill", "calendar", "calendar.badge.clock"]),
|
("Time", ["clock.fill", "alarm.fill", "timer", "hourglass", "sunrise.fill", "sunset.fill", "moon.fill", "moon.stars.fill", "calendar", "calendar.badge.clock"]),
|
||||||
("Home", ["bed.double.fill", "bathtub.fill", "shower.fill", "washer.fill", "house.fill", "lamp.desk.fill", "chair.fill", "sofa.fill", "lightbulb.fill", "fan.fill"]),
|
("Home", ["bed.double.fill", "bathtub.fill", "shower.fill", "washer.fill", "house.fill", "lamp.desk.fill", "chair.fill", "sofa.fill", "lightbulb.fill", "fan.fill", "hammer.fill", "wrench.adjustable.fill", "screwdriver.fill", "sink.fill", "mop.fill"]),
|
||||||
("Work", ["briefcase.fill", "folder.fill", "tray.full.fill", "archivebox.fill", "calendar", "calendar.badge.plus", "checkmark.square.fill", "list.bullet.clipboard.fill", "checklist", "chart.bar.fill", "gauge.with.dots.needle.bottom.50percent", "laptopcomputer"]),
|
("Work", ["briefcase.fill", "folder.fill", "tray.full.fill", "archivebox.fill", "calendar", "calendar.badge.plus", "checkmark.square.fill", "list.bullet.clipboard.fill", "checklist", "chart.bar.fill", "gauge.with.dots.needle.bottom.50percent", "laptopcomputer"]),
|
||||||
("Social", ["person.fill", "person.2.fill", "person.wave.2.fill", "bubble.left.fill", "phone.fill", "envelope.fill", "hand.wave.fill", "message.fill", "video.fill", "hand.thumbsup.fill"]),
|
("Social", ["person.fill", "person.2.fill", "person.wave.2.fill", "bubble.left.fill", "phone.fill", "envelope.fill", "hand.wave.fill", "message.fill", "video.fill", "hand.thumbsup.fill"]),
|
||||||
("Nature", ["sun.max.fill", "cloud.fill", "wind", "snowflake", "tree.fill", "flower.fill", "mountain.2.fill", "water.waves", "leaf.arrow.circlepath", "globe.americas.fill"]),
|
("Nature", ["sun.max.fill", "cloud.fill", "wind", "snowflake", "tree.fill", "flower.fill", "mountain.2.fill", "water.waves", "leaf.arrow.circlepath", "globe.americas.fill", "sprout.fill", "fossil.shell.fill"]),
|
||||||
("Tech", ["iphone", "desktopcomputer", "keyboard.fill", "headphones", "wifi", "antenna.radiowaves.left.and.right", "laptopcomputer", "applewatch", "airpods", "gamecontroller.fill"]),
|
("Tech", ["iphone", "desktopcomputer", "keyboard.fill", "headphones", "wifi", "antenna.radiowaves.left.and.right", "laptopcomputer", "applewatch", "airpods", "gamecontroller.fill"]),
|
||||||
("Finance", ["banknote.fill", "dollarsign.circle.fill", "creditcard.fill", "cart.fill", "bag.fill", "chart.line.uptrend.xyaxis", "building.columns.fill"]),
|
("Finance", ["banknote.fill", "dollarsign.circle.fill", "creditcard.fill", "cart.fill", "bag.fill", "chart.line.uptrend.xyaxis", "building.columns.fill"]),
|
||||||
("Travel", ["airplane", "car.fill", "tram.fill", "ferry.fill", "mappin.circle.fill", "globe.americas.fill", "suitcase.fill", "fuelpump.fill"]),
|
("Travel", ["airplane", "car.fill", "tram.fill", "ferry.fill", "mappin.circle.fill", "globe.americas.fill", "suitcase.fill", "fuelpump.fill"]),
|
||||||
|
("Animals", ["dog.fill", "cat.fill", "pawprint.fill", "bird.fill", "tortoise.fill", "lizard.fill", "fish.fill", "hare.fill", "ladybug.fill"]),
|
||||||
|
("Hobbies", ["camera.shutter.button.fill", "gamecontroller.fill", "puzzlepiece.fill", "die.face.5.fill", "binoculars.fill", "theatermasks.fill", "paintpalette.fill"]),
|
||||||
("Art & Music", ["paintpalette.fill", "paintbrush.fill", "pencil.tip.crop.circle", "scissors", "theatermasks.fill", "music.note", "guitars.fill", "pianokeys", "headphones", "film.fill"]),
|
("Art & Music", ["paintpalette.fill", "paintbrush.fill", "pencil.tip.crop.circle", "scissors", "theatermasks.fill", "music.note", "guitars.fill", "pianokeys", "headphones", "film.fill"]),
|
||||||
("Self-Care", ["comb.fill", "eyebrow", "lips.fill", "hand.raised.fingers.spread.fill", "sparkles", "shower.fill", "bathtub.fill", "leaf.fill"]),
|
("Self-Care", ["comb.fill", "eyebrow", "lips.fill", "hand.raised.fingers.spread.fill", "sparkles", "shower.fill", "bathtub.fill", "leaf.fill"]),
|
||||||
("Cleaning", ["trash.fill", "archivebox.fill", "tshirt.fill", "washer.fill", "sparkles", "bubble.left.and.bubble.right.fill"]),
|
("Cleaning", ["trash.fill", "archivebox.fill", "tshirt.fill", "washer.fill", "sparkles", "bubble.left.and.bubble.right.fill"]),
|
||||||
|
|||||||
@ -8,6 +8,10 @@ struct RootView: View {
|
|||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@State private var selectedTab: RootTab
|
@State private var selectedTab: RootTab
|
||||||
@State private var analyticsPrewarmTask: Task<Void, Never>?
|
@State private var analyticsPrewarmTask: Task<Void, Never>?
|
||||||
|
@State private var isForegroundRefreshing = false
|
||||||
|
private let foregroundRefreshMinimumSeconds: TimeInterval = 0.15
|
||||||
|
private let debugForegroundRefreshMinimumSeconds: TimeInterval = 0.8
|
||||||
|
private let debugForegroundRefreshKey = "debugForegroundRefreshNextForeground"
|
||||||
|
|
||||||
/// The available tabs in the app.
|
/// The available tabs in the app.
|
||||||
enum RootTab: Hashable {
|
enum RootTab: Hashable {
|
||||||
@ -37,47 +41,67 @@ struct RootView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView(selection: $selectedTab) {
|
ZStack {
|
||||||
Tab(String(localized: "Today"), systemImage: "sun.max.fill", value: RootTab.today) {
|
TabView(selection: $selectedTab) {
|
||||||
NavigationStack {
|
Tab(String(localized: "Today"), systemImage: "sun.max.fill", value: RootTab.today) {
|
||||||
TodayView(store: store, categoryStore: categoryStore)
|
NavigationStack {
|
||||||
|
TodayView(store: store, categoryStore: categoryStore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab(String(localized: "Rituals"), systemImage: "sparkles", value: RootTab.rituals) {
|
||||||
|
NavigationStack {
|
||||||
|
RitualsView(store: store, categoryStore: categoryStore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab(String(localized: "Insights"), systemImage: "chart.bar.fill", value: RootTab.insights) {
|
||||||
|
NavigationStack {
|
||||||
|
InsightsView(store: store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab(String(localized: "History"), systemImage: "calendar", value: RootTab.history) {
|
||||||
|
NavigationStack {
|
||||||
|
HistoryView(store: store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab(String(localized: "Settings"), systemImage: "gearshape.fill", value: RootTab.settings) {
|
||||||
|
NavigationStack {
|
||||||
|
SettingsView(store: settingsStore, ritualStore: store, categoryStore: categoryStore)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab(String(localized: "Rituals"), systemImage: "sparkles", value: RootTab.rituals) {
|
if isForegroundRefreshing {
|
||||||
NavigationStack {
|
AppSurface.primary
|
||||||
RitualsView(store: store, categoryStore: categoryStore)
|
.ignoresSafeArea()
|
||||||
}
|
.overlay {
|
||||||
}
|
ProgressView()
|
||||||
|
.tint(AppAccent.primary)
|
||||||
Tab(String(localized: "Insights"), systemImage: "chart.bar.fill", value: RootTab.insights) {
|
}
|
||||||
NavigationStack {
|
.transition(.opacity)
|
||||||
InsightsView(store: store)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Tab(String(localized: "History"), systemImage: "calendar", value: RootTab.history) {
|
|
||||||
NavigationStack {
|
|
||||||
HistoryView(store: store)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Tab(String(localized: "Settings"), systemImage: "gearshape.fill", value: RootTab.settings) {
|
|
||||||
NavigationStack {
|
|
||||||
SettingsView(store: settingsStore, ritualStore: store, categoryStore: categoryStore)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tint(AppAccent.primary)
|
.tint(AppAccent.primary)
|
||||||
.background(AppSurface.primary.ignoresSafeArea())
|
.background(AppSurface.primary.ignoresSafeArea())
|
||||||
|
.animation(.easeInOut(duration: 0.12), value: isForegroundRefreshing)
|
||||||
.onChange(of: scenePhase) { _, newPhase in
|
.onChange(of: scenePhase) { _, newPhase in
|
||||||
if newPhase == .active {
|
if newPhase == .active {
|
||||||
store.reminderScheduler.clearBadge()
|
store.reminderScheduler.clearBadge()
|
||||||
refreshCurrentTab()
|
let useDebugOverlay = UserDefaults.standard.bool(forKey: debugForegroundRefreshKey)
|
||||||
|
if useDebugOverlay {
|
||||||
|
UserDefaults.standard.set(false, forKey: debugForegroundRefreshKey)
|
||||||
|
}
|
||||||
|
refreshAllTabs(
|
||||||
|
showOverlay: true,
|
||||||
|
minimumSeconds: useDebugOverlay ? debugForegroundRefreshMinimumSeconds : foregroundRefreshMinimumSeconds
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: selectedTab) { _, _ in
|
.onChange(of: selectedTab) { _, _ in
|
||||||
refreshCurrentTab()
|
refreshAllTabs(showOverlay: false, minimumSeconds: foregroundRefreshMinimumSeconds)
|
||||||
}
|
}
|
||||||
.onChange(of: store.reminderScheduler.shouldNavigateToToday) { _, shouldNavigate in
|
.onChange(of: store.reminderScheduler.shouldNavigateToToday) { _, shouldNavigate in
|
||||||
if shouldNavigate {
|
if shouldNavigate {
|
||||||
@ -107,23 +131,35 @@ struct RootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refreshCurrentTab() {
|
private func refreshAllTabs(showOverlay: Bool, minimumSeconds: TimeInterval) {
|
||||||
Task {
|
Task { @MainActor in
|
||||||
|
let start = Date()
|
||||||
|
if showOverlay {
|
||||||
|
isForegroundRefreshing = true
|
||||||
|
}
|
||||||
// Let tab selection UI update before refreshing data.
|
// Let tab selection UI update before refreshing data.
|
||||||
await Task.yield()
|
await Task.yield()
|
||||||
store.refresh()
|
if showOverlay {
|
||||||
analyticsPrewarmTask?.cancel()
|
store.refresh()
|
||||||
if selectedTab != .insights {
|
} else {
|
||||||
analyticsPrewarmTask = Task { @MainActor in
|
store.refreshIfNeeded()
|
||||||
try? await Task.sleep(for: .milliseconds(350))
|
|
||||||
guard !Task.isCancelled else { return }
|
|
||||||
store.refreshAnalyticsIfNeeded()
|
|
||||||
store.refreshInsightCardsIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if selectedTab == .settings {
|
analyticsPrewarmTask?.cancel()
|
||||||
settingsStore.refresh()
|
analyticsPrewarmTask = Task { @MainActor in
|
||||||
await store.reminderScheduler.refreshStatus()
|
try? await Task.sleep(for: .milliseconds(350))
|
||||||
|
guard !Task.isCancelled else { return }
|
||||||
|
store.refreshAnalyticsIfNeeded()
|
||||||
|
store.refreshInsightCardsIfNeeded()
|
||||||
|
}
|
||||||
|
settingsStore.refresh()
|
||||||
|
await store.reminderScheduler.refreshStatus()
|
||||||
|
if showOverlay {
|
||||||
|
let elapsed = Date().timeIntervalSince(start)
|
||||||
|
let remaining = max(0, minimumSeconds - elapsed)
|
||||||
|
if remaining > 0 {
|
||||||
|
try? await Task.sleep(for: .seconds(remaining))
|
||||||
|
}
|
||||||
|
isForegroundRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -164,6 +164,14 @@ struct SettingsView: View {
|
|||||||
ritualStore.reminderScheduler.scheduleTestNotification()
|
ritualStore.reminderScheduler.scheduleTestNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsRow(
|
||||||
|
systemImage: "arrow.clockwise",
|
||||||
|
title: String(localized: "Simulate Foreground Refresh"),
|
||||||
|
iconColor: AppStatus.info
|
||||||
|
) {
|
||||||
|
UserDefaults.standard.set(true, forKey: "debugForegroundRefreshNextForeground")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -174,7 +182,7 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
store.refresh()
|
store.refresh()
|
||||||
ritualStore?.refresh()
|
ritualStore?.refreshIfNeeded()
|
||||||
Task {
|
Task {
|
||||||
await ritualStore?.reminderScheduler.refreshStatus()
|
await ritualStore?.reminderScheduler.refreshStatus()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,9 @@ struct TodayView: View {
|
|||||||
ArcRenewalSheet(store: store, categoryStore: categoryStore, ritual: ritual)
|
ArcRenewalSheet(store: store, categoryStore: categoryStore, ritual: ritual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
store.refreshIfNeeded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func habitRows(for ritual: Ritual) -> [HabitRowModel] {
|
private func habitRows(for ritual: Ritual) -> [HabitRowModel] {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user