diff options
| author | Christian Cleberg <[email protected]> | 2026-04-01 12:04:31 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-01 12:04:31 -0500 |
| commit | 367a9d8e9592d0c604356e74edea97e0791b32cd (patch) | |
| tree | d99bae033a8b962f67b51ceb11f8982b114f358b /HutchWidgetExtension | |
| parent | c22287d5e256f5b23528114c5efec63069f9c2b5 (diff) | |
| download | hutch-367a9d8e9592d0c604356e74edea97e0791b32cd.tar.gz hutch-367a9d8e9592d0c604356e74edea97e0791b32cd.tar.bz2 hutch-367a9d8e9592d0c604356e74edea97e0791b32cd.zip | |
fix: add large widget and apply UX fixes for all widget sizes
Implements: https://todo.sr.ht/~ccleberg/Hutch/8
Diffstat (limited to 'HutchWidgetExtension')
| -rw-r--r-- | HutchWidgetExtension/NeedsAttentionWidget.swift | 132 |
1 files changed, 106 insertions, 26 deletions
diff --git a/HutchWidgetExtension/NeedsAttentionWidget.swift b/HutchWidgetExtension/NeedsAttentionWidget.swift index 0e177a7..6191d20 100644 --- a/HutchWidgetExtension/NeedsAttentionWidget.swift +++ b/HutchWidgetExtension/NeedsAttentionWidget.swift @@ -44,7 +44,7 @@ struct NeedsAttentionWidget: Widget { } .configurationDisplayName("Needs Attention") .description("Unread inbox threads, assigned tickets, and failed builds.") - .supportedFamilies([.systemSmall, .systemMedium]) + .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) } } @@ -61,6 +61,8 @@ private struct NeedsAttentionWidgetView: View { switch family { case .systemMedium: mediumView(snapshot: snapshot) + case .systemLarge: + largeView(snapshot: snapshot) default: smallView(snapshot: snapshot) } @@ -75,17 +77,6 @@ private struct NeedsAttentionWidgetView: View { } } - private var header: some View { - VStack(alignment: .leading, spacing: 1) { - Text("Hutch") - .font(.caption.weight(.semibold)) - .foregroundStyle(.secondary) - Text("Needs Attention") - .font(.headline) - .lineLimit(1) - } - } - private func smallView(snapshot: NeedsAttentionSnapshot) -> some View { VStack(alignment: .leading, spacing: 12) { Spacer(minLength: 0) @@ -108,9 +99,7 @@ private struct NeedsAttentionWidgetView: View { } private func mediumView(snapshot: NeedsAttentionSnapshot) -> some View { - VStack(alignment: .leading, spacing: 18) { - header - .padding(.top, 6) + VStack(alignment: .leading, spacing: 0) { HStack(alignment: .top, spacing: 12) { metricColumn( count: snapshot.unreadInboxThreads, @@ -137,23 +126,47 @@ private struct NeedsAttentionWidgetView: View { .padding(.bottom, 16) } - private var clearState: some View { - VStack(alignment: .leading, spacing: 12) { - header + private func largeView(snapshot: NeedsAttentionSnapshot) -> some View { + VStack(alignment: .leading, spacing: 18) { + metricRow( + count: snapshot.unreadInboxThreads, + label: "Unread threads", + systemImage: "tray", + tint: unreadTint(for: snapshot.unreadInboxThreads) + ) + metricRow( + count: snapshot.assignedOpenTickets, + label: "Assigned tickets", + systemImage: "ticket" + ) + metricRow( + count: snapshot.failedBuilds, + label: "Failed builds", + systemImage: "hammer", + tint: failedTint(for: snapshot.failedBuilds) + ) Spacer(minLength: 0) - Text("All clear") - .font(.title3.weight(.semibold)) - Text("No unread threads, assigned tickets, or failed builds.") - .font(.caption) - .foregroundStyle(.secondary) - .fixedSize(horizontal: false, vertical: true) } - .padding() + .padding(20) + } + + private var clearState: some View { + Group { + switch family { + case .systemSmall: + smallClearState + case .systemMedium: + mediumClearState + case .systemLarge: + largeClearState + default: + smallClearState + } + } } private var fallbackState: some View { VStack(alignment: .leading, spacing: 12) { - header Spacer(minLength: 0) Text("Open Hutch") .font(.title3.weight(.semibold)) @@ -202,6 +215,73 @@ private struct NeedsAttentionWidgetView: View { .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } + private func metricRow(count: Int?, label: String, systemImage: String, tint: Color = .primary) -> some View { + HStack(alignment: .center, spacing: 12) { + Image(systemName: systemImage) + .font(.title3.weight(.semibold)) + .foregroundStyle(tint) + .frame(width: 24, alignment: .leading) + Text(displayCount(count)) + .font(.system(size: 30, weight: .semibold, design: .rounded)) + .monospacedDigit() + .foregroundStyle(tint) + Text(label) + .font(.body) + .foregroundStyle(.secondary) + Spacer(minLength: 0) + } + } + + private var smallClearState: some View { + VStack(spacing: 6) { + Spacer(minLength: 0) + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 52, weight: .regular)) + .foregroundStyle(clearStateTint) + Text("0 items") + .font(.caption2.weight(.semibold)) + .foregroundStyle(.secondary) + .lineLimit(1) + Spacer(minLength: 0) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding() + } + + private var mediumClearState: some View { + VStack(spacing: 10) { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 26, weight: .regular)) + .foregroundStyle(clearStateTint) + Text("Nothing to review") + .font(.body.weight(.semibold)) + .foregroundStyle(.primary) + .lineLimit(1) + .minimumScaleFactor(0.9) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + .padding() + } + + private var largeClearState: some View { + VStack(spacing: 12) { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 38, weight: .regular)) + .foregroundStyle(clearStateTint) + Text("Nothing to review") + .font(.title3.weight(.semibold)) + .foregroundStyle(.primary) + .lineLimit(1) + .minimumScaleFactor(0.9) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + .padding() + } + + private var clearStateTint: Color { + Color(.systemGreen).opacity(0.75) + } + private func displayCount(_ count: Int?) -> String { guard let count else { return "—" } return "\(count)" |
