diff --git a/Apps/Styles/Labels/NotificationLabelStyle.swift b/Apps/Styles/Labels/NotificationLabelStyle.swift new file mode 100644 index 0000000..2ac365f --- /dev/null +++ b/Apps/Styles/Labels/NotificationLabelStyle.swift @@ -0,0 +1,145 @@ +import SwiftUI + +/// The label style for the transient in-app notification banners. +/// +/// The style renders the label's icon next to its title, over a padded, capsule Liquid Glass background. The icon is tinted with a color +/// matching the download event given at initialization — green for a started download, yellow for a cancelled one, and red for a +/// failed one — and the title wears a medium-weight callout font. +struct NotificationLabelStyle: LabelStyle { + + // MARK: Properties + + /// The download event whose matching color tints the label's icon. + private let event: AppNotification.Event + + // MARK: Initializers + + /// Creates a notification label style for a given download event. + /// + /// - Parameter event: The download event whose matching color tints the label's icon. + fileprivate init( + event: AppNotification.Event + ) { + self.event = event + } + + // MARK: Methods + + /// Creates the styled body of the label. + /// + /// - Parameter configuration: The properties of the label being styled. + public func makeBody( + configuration: Configuration + ) -> some View { + HStack( + spacing: Constant.Spacing.stack + ) { + configuration.icon + .font(.title3) + .foregroundStyle(event.imageStyle) + + configuration.title + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.primary) + } + .padding(.vertical, Constant.Padding.vertical) + .padding(.horizontal, Constant.Padding.horizontal) + .glassEffect( + .regular, + in: .capsule + ) + } + +} + +// MARK: - Styles + +extension LabelStyle where Self == NotificationLabelStyle { + + /// The label style for the transient in-app notification banners. + /// + /// - Parameter event: The download event whose matching color tints the label's icon. + /// - Returns: A notification label style for the given event. + static func notification( + event: AppNotification.Event + ) -> NotificationLabelStyle { + .init(event: event) + } + +} + +// MARK: - AppNotification.Event+Properties + +private extension AppNotification.Event { + + /// The color that tints the icon of a notification label: green for a started download, yellow for a cancelled one, and red + /// for a failed one. + var imageStyle: Color { + switch self { + case .cancelled: .yellow + case .failed: .red + case .started: .green + } + } + +} + + +// MARK: - Constants + +/// The constant values used across the label style. +private enum Constant { + /// The padding constants. + enum Padding { + /// The horizontal padding between a label's content and its background. + static let horizontal: CGFloat = 16 + /// The vertical padding between a label's content and its background. + static let vertical: CGFloat = 8 + } + + /// The spacing constants. + enum Spacing { + /// The spacing between the elements of a stack. + static let stack: CGFloat = 16 + } +} + +// MARK: - Previews + +#Preview( + "Notification label style" +) { + VStack( + alignment: .leading, + spacing: 16 + ) { + Label { + Text(verbatim: "Downloading the speech model for Dutch (Netherlands)…") + } icon: { + Image(systemName: "arrow.down.circle") + } + .labelStyle(.notification( + event: .started + )) + + Label { + Text(verbatim: "The speech model download for Dutch (Netherlands) was cancelled.") + } icon: { + Image(systemName: "xmark.circle") + } + .labelStyle(.notification( + event: .cancelled + )) + + Label { + Text(verbatim: "The speech model for German (Germany) could not be downloaded.") + } icon: { + Image(systemName: "exclamationmark.triangle") + } + .labelStyle(.notification( + event: .failed + )) + } + +} diff --git a/Attendi.xcodeproj/project.pbxproj b/Attendi.xcodeproj/project.pbxproj index f2e929b..fcf654f 100644 --- a/Attendi.xcodeproj/project.pbxproj +++ b/Attendi.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ Attendi/Models/AppNotification.swift, "Attendi/View Models/ContentViewModel.swift", Attendi/Views/ContentView.swift, + Styles/Labels/NotificationLabelStyle.swift, ); target = 02870A0C2FF7EB610079EA3A /* Attendi */; };