commit 6ea98d378d7dcb99deb2edb1b98b415c53778cf9
parent b2122609e4db8d334a922ca9e46d73b085c72be1
Author: Michael Camilleri <[email protected]>
Date: Fri, 3 Jul 2026 15:57:45 +0900
Vary announcement sizes depending on context
Diffstat:
2 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/Crossmate/Views/Components/AnnouncementBanner.swift b/Crossmate/Views/Components/AnnouncementBanner.swift
@@ -4,6 +4,39 @@ import SwiftUI
/// and body text over a neutral material background. Manual announcements get
/// a close affordance; transient and sticky announcements do not.
struct AnnouncementBanner: View {
+ enum ContentSize {
+ case compact
+ case prominent
+
+ var childSpacing: CGFloat {
+ switch self {
+ case .compact: return 8
+ case .prominent: return 10
+ }
+ }
+
+ var iconFont: Font {
+ switch self {
+ case .compact: return .footnote.weight(.semibold)
+ case .prominent: return .subheadline.weight(.semibold)
+ }
+ }
+
+ var titleFont: Font {
+ switch self {
+ case .compact: return .subheadline.weight(.semibold)
+ case .prominent: return .callout.weight(.semibold)
+ }
+ }
+
+ var bodyFont: Font {
+ switch self {
+ case .compact: return .subheadline
+ case .prominent: return .callout
+ }
+ }
+ }
+
let announcement: Announcement
/// When true the banner stretches to the height its container proposes
/// (PuzzleView's fixed-height header slot); by default it hugs its text,
@@ -12,6 +45,9 @@ struct AnnouncementBanner: View {
/// Whether to show the leading severity icon. The Settings tips archive
/// turns it off — the rail alone carries the tip styling there.
var showsIcon = true
+ /// Visual density for the banner content. Puzzle-header announcements use
+ /// the compact default; Game List announcements opt into larger text.
+ var contentSize: ContentSize = .compact
/// Fill behind the banner. Defaults to the inset-grouped section fill so
/// banners read as solid cards on the grouped Game List and in the Settings
/// tips archive; override per surface if a different fill is wanted.
@@ -20,12 +56,10 @@ struct AnnouncementBanner: View {
var body: some View {
let severityColor = tint(for: announcement.severity)
- // 10pt between children so the icon gets the same air on its trailing
- // side as it has between itself and the severity rail.
- HStack(alignment: .center, spacing: 10) {
+ HStack(alignment: .center, spacing: contentSize.childSpacing) {
if showsIcon {
Image(systemName: iconName(for: announcement.severity))
- .font(.subheadline.weight(.semibold))
+ .font(contentSize.iconFont)
.foregroundStyle(severityColor)
.frame(width: 24)
.accessibilityHidden(true)
@@ -34,11 +68,11 @@ struct AnnouncementBanner: View {
VStack(alignment: .leading, spacing: 3) {
if let title = announcement.title, !title.isEmpty {
Text(title)
- .font(.callout.weight(.semibold))
+ .font(contentSize.titleFont)
.lineLimit(1)
}
Text(announcement.body)
- .font(.callout)
+ .font(contentSize.bodyFont)
.foregroundStyle(.primary)
.multilineTextAlignment(.leading)
.lineLimit(3)
@@ -61,10 +95,7 @@ struct AnnouncementBanner: View {
.padding(.horizontal, 10)
.padding(.vertical, 10)
// Without the icon the text would otherwise crowd the severity rail;
- // give it a little extra breathing room on the leading edge. With the
- // icon, the extra 4pt makes the rail-to-icon gap match the 10pt
- // HStack spacing on the icon's other side (the rail eats 4pt of the
- // 10pt base padding).
+ // give it a little extra breathing room on the leading edge.
.padding(.leading, showsIcon ? 4 : 12)
.frame(
maxWidth: .infinity,
diff --git a/Crossmate/Views/GameList/GameListView.swift b/Crossmate/Views/GameList/GameListView.swift
@@ -73,7 +73,7 @@ struct GameListView: View {
var body: some View {
VStack(spacing: 0) {
if let announcement = announcements.currentGlobal() {
- AnnouncementBanner(announcement: announcement) {
+ AnnouncementBanner(announcement: announcement, contentSize: .prominent) {
dismissAnnouncement(announcement)
}
.padding(.horizontal)