From b25ac7621d1a7a2bbdf19588d855da531a185c55 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Sat, 11 Apr 2026 19:55:28 -0500 Subject: fix: respect contribution graphs toggle in widget Mirror the contributionGraphsEnabled setting to shared app group defaults so the widget extension can read it. Show a descriptive message when contributions are disabled instead of placeholder data. --- Hutch/Views/Settings/SettingsView.swift | 3 ++ HutchWidgetExtension/ContributionGraphWidget.swift | 46 ++++++++++++++++++++-- Shared/ContributionWidgetContext.swift | 10 +++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/Hutch/Views/Settings/SettingsView.swift b/Hutch/Views/Settings/SettingsView.swift index a249807..4fb3f9c 100644 --- a/Hutch/Views/Settings/SettingsView.swift +++ b/Hutch/Views/Settings/SettingsView.swift @@ -49,6 +49,9 @@ struct SettingsView: View { Section { Toggle("Swipe actions", isOn: $swipeActionsEnabled) Toggle("Contribution graphs", isOn: $contributionGraphsEnabled) + .onChange(of: contributionGraphsEnabled) { _, newValue in + ContributionWidgetContextStore.setEnabled(newValue) + } } header: { Text("Behavior") } footer: { diff --git a/HutchWidgetExtension/ContributionGraphWidget.swift b/HutchWidgetExtension/ContributionGraphWidget.swift index c690f54..5dbf6a2 100644 --- a/HutchWidgetExtension/ContributionGraphWidget.swift +++ b/HutchWidgetExtension/ContributionGraphWidget.swift @@ -9,6 +9,7 @@ struct ContributionGraphEntry: TimelineEntry { enum State { case placeholder + case disabled case unavailable case empty case indexing @@ -41,6 +42,10 @@ struct ContributionGraphTimelineProvider: TimelineProvider { } private func loadEntry() async -> ContributionGraphEntry { + guard ContributionWidgetContextStore.isEnabled() else { + return ContributionGraphEntry(date: .now, actor: nil, state: .disabled, weeks: []) + } + guard let actor = ContributionWidgetContextStore.loadActor(), !actor.isEmpty else { return ContributionGraphEntry(date: .now, actor: nil, state: .unavailable, weeks: []) } @@ -91,6 +96,29 @@ private struct ContributionGraphWidgetView: View { @Environment(\.widgetFamily) private var family var body: some View { + Group { + switch entry.state { + case .disabled: + messageView( + title: "Contributions Disabled", + detail: "Enable contribution graphs in Hutch settings to use this widget." + ) + case .unavailable: + messageView( + title: "Open Hutch", + detail: "Sign in and open your profile to load contribution data." + ) + default: + graphView + } + } + .widgetURL(URL(string: "hutch://home")) + .containerBackground(for: .widget) { + Color(.systemBackground) + } + } + + private var graphView: some View { GeometryReader { geometry in let cellSize = ContributionGraphSizing.baseCellSize(availableHeight: geometry.size.height) let layout = ContributionGraphSizing.layout(availableSize: geometry.size, cellSize: cellSize) @@ -108,17 +136,27 @@ private struct ContributionGraphWidgetView: View { .frame(width: actualSize.width, height: actualSize.height) .frame(maxWidth: .infinity, maxHeight: .infinity) } - .widgetURL(URL(string: "hutch://home")) - .containerBackground(for: .widget) { - Color(.systemBackground) + } + + private func messageView(title: String, detail: String) -> some View { + VStack(spacing: 6) { + Text(title) + .font(family == .systemSmall ? .headline : .title3.weight(.semibold)) + .multilineTextAlignment(.center) + Text(detail) + .font(.caption) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) } + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity) } private func displayedWeeks(columnCount: Int) -> [ContributionGraphWeek] { var baseWeeks = switch entry.state { case .populated, .indexing, .empty: entry.weeks - case .placeholder, .unavailable: + case .placeholder, .disabled, .unavailable: ContributionGraphSampleData.placeholderWeeks } diff --git a/Shared/ContributionWidgetContext.swift b/Shared/ContributionWidgetContext.swift index 3fadc8a..2404698 100644 --- a/Shared/ContributionWidgetContext.swift +++ b/Shared/ContributionWidgetContext.swift @@ -9,11 +9,21 @@ enum ContributionGraphWidgetConfiguration { enum ContributionWidgetContextStore { private static let actorKey = "contributionWidget.actor" + private static let enabledKey = "contributionWidget.enabled" static func loadActor(defaults: UserDefaults? = sharedDefaults()) -> String? { defaults?.string(forKey: actorKey) } + static func isEnabled(defaults: UserDefaults? = sharedDefaults()) -> Bool { + defaults?.object(forKey: enabledKey) == nil ? true : defaults?.bool(forKey: enabledKey) ?? true + } + + static func setEnabled(_ enabled: Bool, defaults: UserDefaults? = sharedDefaults()) { + defaults?.set(enabled, forKey: enabledKey) + reloadWidgetTimelines() + } + static func saveActor(_ actor: String, defaults: UserDefaults? = sharedDefaults()) { defaults?.set(actor, forKey: actorKey) reloadWidgetTimelines() -- cgit v1.2.3