Extracted the in-app notifications from the Sample app target into its own Notifying package target.

This commit is contained in:
2026-07-05 13:33:07 +02:00
parent 6bcb88c944
commit 59b0237638
8 changed files with 278 additions and 106 deletions
-73
View File
@@ -1,73 +0,0 @@
import Foundation
/// An in-app notification reporting an event of a speech model asset download.
struct AppNotification: Equatable, Identifiable {
// MARK: Properties
/// The unique identifier of the notification.
let id: UUID
/// The download event the notification reports.
let event: Event
/// The localized name of the locale whose speech model the event is about.
let localeName: String
// MARK: Initializers
/// Creates a notification for a given download event and locale name.
///
/// - Parameters:
/// - id: The unique identifier of the notification. Defaults to a newly generated identifier.
/// - event: The download event the notification reports.
/// - localeName: The localized name of the locale whose speech model the event is about.
init(
id: UUID = .init(),
event: Event,
localeName: String
) {
self.id = id
self.event = event
self.localeName = localeName
}
}
// MARK: - Enumerations
extension AppNotification {
/// The download event the notification reports.
enum Event: Sendable {
/// The download was cancelled before it finished.
case cancelled
/// The download failed.
case failed
/// The download started.
case started
}
}
// MARK: - Properties
extension AppNotification {
/// The system symbol of the icon matching the notification's event.
var imageSymbol: String {
switch event {
case .cancelled: "xmark.circle"
case .failed: "exclamationmark.triangle"
case .started: "arrow.down.circle"
}
}
/// The localized message of the notification, naming the locale of the speech model, from the app's string catalog.
var textMessage: LocalizedStringResource {
switch event {
case .cancelled: .viewRecordingNotificationDownloadCancelled(localeName)
case .failed: .viewRecordingNotificationDownloadFailed(localeName)
case .started: .viewRecordingNotificationDownloadStarted(localeName)
}
}
}
@@ -1,145 +0,0 @@
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
))
}
}