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() {
|
func clearBadge() {
|
||||||
UNUserNotificationCenter.current().setBadgeCount(0) { _ in }
|
UNUserNotificationCenter.current().setBadgeCount(0) { _ in }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Refreshes authorization status and reschedules if enabled.
|
||||||
|
func refreshStatus() async {
|
||||||
|
await refreshAuthorizationStatus()
|
||||||
|
if remindersEnabled {
|
||||||
|
await scheduleRemindersForActiveSlots()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,12 @@ final class RitualStore: RitualStoreProviding {
|
|||||||
return Double(completed) / Double(habits.count)
|
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 {
|
func ritualProgress(for ritual: Ritual) -> Double {
|
||||||
let habits = ritual.habits
|
let habits = ritual.habits
|
||||||
guard !habits.isEmpty else { return 0 }
|
guard !habits.isEmpty else { return 0 }
|
||||||
|
|||||||
@ -32,6 +32,10 @@ final class SettingsStore: CloudSyncable {
|
|||||||
cloudSync.sync()
|
cloudSync.sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refresh() {
|
||||||
|
cloudSync.sync()
|
||||||
|
}
|
||||||
|
|
||||||
private func update(_ transform: (inout AppSettingsData) -> Void) {
|
private func update(_ transform: (inout AppSettingsData) -> Void) {
|
||||||
cloudSync.update { data in
|
cloudSync.update { data in
|
||||||
transform(&data)
|
transform(&data)
|
||||||
|
|||||||
@ -87,6 +87,9 @@ struct HistoryView: View {
|
|||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
))
|
))
|
||||||
|
.onAppear {
|
||||||
|
store.refresh()
|
||||||
|
}
|
||||||
.sheet(item: $selectedDateItem) { item in
|
.sheet(item: $selectedDateItem) { item in
|
||||||
HistoryDayDetailSheet(
|
HistoryDayDetailSheet(
|
||||||
date: item.date,
|
date: item.date,
|
||||||
|
|||||||
@ -29,6 +29,9 @@ struct InsightsView: View {
|
|||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
))
|
))
|
||||||
|
.onAppear {
|
||||||
|
store.refresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,26 +30,9 @@ struct RitualCardView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
||||||
HStack(spacing: Design.Spacing.small) {
|
ViewThatFits(in: .horizontal) {
|
||||||
// Icon
|
wideHeader
|
||||||
Image(systemName: iconName)
|
compactHeader
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(theme)
|
Text(theme)
|
||||||
@ -67,6 +50,74 @@ struct RitualCardView: View {
|
|||||||
.accessibilityElement(children: .combine)
|
.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 {
|
private var timeOfDayBadge: some View {
|
||||||
VStack(alignment: .trailing, spacing: 2) {
|
VStack(alignment: .trailing, spacing: 2) {
|
||||||
HStack(spacing: Design.Spacing.xSmall) {
|
HStack(spacing: Design.Spacing.xSmall) {
|
||||||
|
|||||||
@ -47,6 +47,9 @@ struct RitualsView: View {
|
|||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
))
|
))
|
||||||
|
.onAppear {
|
||||||
|
store.refresh()
|
||||||
|
}
|
||||||
.navigationTitle(String(localized: "Rituals"))
|
.navigationTitle(String(localized: "Rituals"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|||||||
@ -6,34 +6,44 @@ struct RootView: View {
|
|||||||
@Bindable var store: RitualStore
|
@Bindable var store: RitualStore
|
||||||
@Bindable var settingsStore: SettingsStore
|
@Bindable var settingsStore: SettingsStore
|
||||||
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
|
@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 {
|
var body: some View {
|
||||||
TabView {
|
TabView(selection: $selectedTab) {
|
||||||
Tab(String(localized: "Today"), systemImage: "sun.max.fill") {
|
Tab(String(localized: "Today"), systemImage: "sun.max.fill", value: RootTab.today) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
TodayView(store: store)
|
TodayView(store: store)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab(String(localized: "Rituals"), systemImage: "sparkles") {
|
Tab(String(localized: "Rituals"), systemImage: "sparkles", value: RootTab.rituals) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
RitualsView(store: store)
|
RitualsView(store: store)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab(String(localized: "Insights"), systemImage: "chart.bar.fill") {
|
Tab(String(localized: "Insights"), systemImage: "chart.bar.fill", value: RootTab.insights) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
InsightsView(store: store)
|
InsightsView(store: store)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab(String(localized: "History"), systemImage: "calendar") {
|
Tab(String(localized: "History"), systemImage: "calendar", value: RootTab.history) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
HistoryView(store: store)
|
HistoryView(store: store)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab(String(localized: "Settings"), systemImage: "gearshape.fill") {
|
Tab(String(localized: "Settings"), systemImage: "gearshape.fill", value: RootTab.settings) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
SettingsView(store: settingsStore, ritualStore: store)
|
SettingsView(store: settingsStore, ritualStore: store)
|
||||||
}
|
}
|
||||||
@ -48,6 +58,21 @@ struct RootView: View {
|
|||||||
delegate: self,
|
delegate: self,
|
||||||
startDelay: Bedrock.Design.Animation.standard
|
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)
|
.padding(.horizontal, Design.Spacing.large)
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
store.refresh()
|
||||||
|
ritualStore?.refresh()
|
||||||
|
Task {
|
||||||
|
await ritualStore?.reminderScheduler.refreshStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
.background(AppSurface.primary)
|
.background(AppSurface.primary)
|
||||||
.navigationTitle(String(localized: "Settings"))
|
.navigationTitle(String(localized: "Settings"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
|||||||
@ -55,7 +55,7 @@ struct TodayView: View {
|
|||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
))
|
))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
store.checkForCompletedArcs()
|
store.refresh()
|
||||||
}
|
}
|
||||||
.sheet(isPresented: .init(
|
.sheet(isPresented: .init(
|
||||||
get: { showRenewalSheet },
|
get: { showRenewalSheet },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user