Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
6aed4d319d
commit
c29ae2bf74
@ -115,6 +115,14 @@ final class ReminderScheduler {
|
||||
func clearBadge() {
|
||||
UNUserNotificationCenter.current().setBadgeCount(0) { _ in }
|
||||
}
|
||||
|
||||
/// Refreshes authorization status and reschedules if enabled.
|
||||
func refreshStatus() async {
|
||||
await refreshAuthorizationStatus()
|
||||
if remindersEnabled {
|
||||
await scheduleRemindersForActiveSlots()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
|
||||
@ -62,6 +62,12 @@ final class RitualStore: RitualStoreProviding {
|
||||
return Double(completed) / Double(habits.count)
|
||||
}
|
||||
|
||||
/// Refreshes rituals and derived state for current date/time.
|
||||
func refresh() {
|
||||
reloadRituals()
|
||||
checkForCompletedArcs()
|
||||
}
|
||||
|
||||
func ritualProgress(for ritual: Ritual) -> Double {
|
||||
let habits = ritual.habits
|
||||
guard !habits.isEmpty else { return 0 }
|
||||
|
||||
@ -32,6 +32,10 @@ final class SettingsStore: CloudSyncable {
|
||||
cloudSync.sync()
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
cloudSync.sync()
|
||||
}
|
||||
|
||||
private func update(_ transform: (inout AppSettingsData) -> Void) {
|
||||
cloudSync.update { data in
|
||||
transform(&data)
|
||||
|
||||
@ -87,6 +87,9 @@ struct HistoryView: View {
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
))
|
||||
.onAppear {
|
||||
store.refresh()
|
||||
}
|
||||
.sheet(item: $selectedDateItem) { item in
|
||||
HistoryDayDetailSheet(
|
||||
date: item.date,
|
||||
|
||||
@ -29,6 +29,9 @@ struct InsightsView: View {
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
))
|
||||
.onAppear {
|
||||
store.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,26 +30,9 @@ struct RitualCardView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
// Icon
|
||||
Image(systemName: iconName)
|
||||
.foregroundStyle(hasActiveArc ? AppAccent.primary : AppTextColors.tertiary)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
// Title
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.foregroundStyle(hasActiveArc ? AppTextColors.primary : AppTextColors.tertiary)
|
||||
|
||||
Spacer(minLength: Design.Spacing.medium)
|
||||
|
||||
// Time of day badge - more prominent
|
||||
timeOfDayBadge
|
||||
|
||||
// Day label
|
||||
Text(dayLabel)
|
||||
.font(.caption)
|
||||
.foregroundStyle(AppTextColors.secondary)
|
||||
ViewThatFits(in: .horizontal) {
|
||||
wideHeader
|
||||
compactHeader
|
||||
}
|
||||
|
||||
Text(theme)
|
||||
@ -67,6 +50,74 @@ struct RitualCardView: View {
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
|
||||
// MARK: - Wide Layout (tablets/landscape)
|
||||
|
||||
private var wideHeader: some View {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Image(systemName: iconName)
|
||||
.foregroundStyle(hasActiveArc ? AppAccent.primary : AppTextColors.tertiary)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.foregroundStyle(hasActiveArc ? AppTextColors.primary : AppTextColors.tertiary)
|
||||
|
||||
Spacer(minLength: Design.Spacing.medium)
|
||||
|
||||
timeOfDayBadge
|
||||
|
||||
Text(dayLabel)
|
||||
.font(.caption)
|
||||
.foregroundStyle(AppTextColors.secondary)
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Compact Layout (phones/portrait)
|
||||
|
||||
private var compactHeader: some View {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Image(systemName: iconName)
|
||||
.foregroundStyle(hasActiveArc ? AppAccent.primary : AppTextColors.tertiary)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.foregroundStyle(hasActiveArc ? AppTextColors.primary : AppTextColors.tertiary)
|
||||
}
|
||||
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
compactTimeOfDayBadge
|
||||
|
||||
Text(dayLabel)
|
||||
.font(.caption)
|
||||
.foregroundStyle(AppTextColors.secondary)
|
||||
.padding(.horizontal, Design.Spacing.small)
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
.background(AppSurface.secondary)
|
||||
.clipShape(.capsule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Time Badges
|
||||
|
||||
private var compactTimeOfDayBadge: some View {
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
Image(systemName: timeOfDay.symbolName)
|
||||
.font(.caption2)
|
||||
Text(timeOfDay.displayName)
|
||||
.font(.caption2)
|
||||
}
|
||||
.foregroundStyle(timeOfDayColor)
|
||||
.padding(.horizontal, Design.Spacing.small)
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
.background(timeOfDayColor.opacity(0.15))
|
||||
.clipShape(.capsule)
|
||||
.accessibilityLabel(timeOfDay.displayNameWithRange)
|
||||
}
|
||||
|
||||
private var timeOfDayBadge: some View {
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
|
||||
@ -47,6 +47,9 @@ struct RitualsView: View {
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
))
|
||||
.onAppear {
|
||||
store.refresh()
|
||||
}
|
||||
.navigationTitle(String(localized: "Rituals"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@ -6,34 +6,44 @@ struct RootView: View {
|
||||
@Bindable var store: RitualStore
|
||||
@Bindable var settingsStore: SettingsStore
|
||||
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
@State private var selectedTab: RootTab = .today
|
||||
|
||||
enum RootTab: Hashable {
|
||||
case today
|
||||
case rituals
|
||||
case insights
|
||||
case history
|
||||
case settings
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
Tab(String(localized: "Today"), systemImage: "sun.max.fill") {
|
||||
TabView(selection: $selectedTab) {
|
||||
Tab(String(localized: "Today"), systemImage: "sun.max.fill", value: RootTab.today) {
|
||||
NavigationStack {
|
||||
TodayView(store: store)
|
||||
}
|
||||
}
|
||||
|
||||
Tab(String(localized: "Rituals"), systemImage: "sparkles") {
|
||||
Tab(String(localized: "Rituals"), systemImage: "sparkles", value: RootTab.rituals) {
|
||||
NavigationStack {
|
||||
RitualsView(store: store)
|
||||
}
|
||||
}
|
||||
|
||||
Tab(String(localized: "Insights"), systemImage: "chart.bar.fill") {
|
||||
Tab(String(localized: "Insights"), systemImage: "chart.bar.fill", value: RootTab.insights) {
|
||||
NavigationStack {
|
||||
InsightsView(store: store)
|
||||
}
|
||||
}
|
||||
|
||||
Tab(String(localized: "History"), systemImage: "calendar") {
|
||||
Tab(String(localized: "History"), systemImage: "calendar", value: RootTab.history) {
|
||||
NavigationStack {
|
||||
HistoryView(store: store)
|
||||
}
|
||||
}
|
||||
|
||||
Tab(String(localized: "Settings"), systemImage: "gearshape.fill") {
|
||||
Tab(String(localized: "Settings"), systemImage: "gearshape.fill", value: RootTab.settings) {
|
||||
NavigationStack {
|
||||
SettingsView(store: settingsStore, ritualStore: store)
|
||||
}
|
||||
@ -48,6 +58,21 @@ struct RootView: View {
|
||||
delegate: self,
|
||||
startDelay: Bedrock.Design.Animation.standard
|
||||
)
|
||||
.onChange(of: scenePhase) { _, newPhase in
|
||||
if newPhase == .active {
|
||||
refreshCurrentTab()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshCurrentTab() {
|
||||
store.refresh()
|
||||
if selectedTab == .settings {
|
||||
settingsStore.refresh()
|
||||
Task {
|
||||
await store.reminderScheduler.refreshStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -136,6 +136,13 @@ struct SettingsView: View {
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
}
|
||||
.onAppear {
|
||||
store.refresh()
|
||||
ritualStore?.refresh()
|
||||
Task {
|
||||
await ritualStore?.reminderScheduler.refreshStatus()
|
||||
}
|
||||
}
|
||||
.background(AppSurface.primary)
|
||||
.navigationTitle(String(localized: "Settings"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
@ -55,7 +55,7 @@ struct TodayView: View {
|
||||
endPoint: .bottomTrailing
|
||||
))
|
||||
.onAppear {
|
||||
store.checkForCompletedArcs()
|
||||
store.refresh()
|
||||
}
|
||||
.sheet(isPresented: .init(
|
||||
get: { showRenewalSheet },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user