Skip to content

Commit

Permalink
Add predefined message styles and types
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsaidi committed Apr 24, 2024
1 parent 61fed4e commit a35c4d1
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 25 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ struct MyView: View {

The `SystemNotificationMessage` view lets you easily mimic a native notification view, with an icon, title and text, but you can use any custom view as the notification body.

For more information about how to configure and style your notifications, please see the [getting started guide][Getting-Started].
For more information about how to configure and style your notifications, predefined message types and styles, and how to create your own custom message types, please see the [getting started guide][Getting-Started].



Expand Down
12 changes: 12 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ Until then, breaking changes can happen in any version, and deprecated features



## 1.1

This version adds predefined system notification messages and styles and makes it easier to present a message.

### ✨ New features

* `SystemNotificationContext` has a new `presentMessage` function.
* `SystemNotificationMessage` has new, predefined `error`, `success`, `warning` and `silentMode` messages.
* `SystemNotificationMessageStyle` has new, predefined `prominent`, `error`, `success` and `warning` styles.



## 1.0

This version bumps the deployment targets and moves styling and configuration to view modifiers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct MyView: View {
}
```

Context-based notifications just take a ``SystemNotificationContext`` instance and can then show many different notifications with a single modifier:
Context-based notifications take a ``SystemNotificationContext`` and can then show different notifications with a single modifier:

```swift
import SystemNotification
Expand Down Expand Up @@ -83,14 +83,28 @@ struct MyView: View {

The ``SystemNotificationMessage`` view lets you easily mimic a native notification view, with an icon, an optional title and a text, but you can use any custom view as the notification content view.

You can use the ``SwiftUI/View/systemNotificationConfiguration(_:)`` and ``SwiftUI/View/systemNotificationStyle(_:)`` view modifiers to apply custom configurations and styles, and the ``SwiftUI/View/systemNotificationMessageStyle(_:)`` to style the message.
You can use the ``SwiftUI/View/systemNotificationConfiguration(_:)`` and ``SwiftUI/View/systemNotificationStyle(_:)`` view modifiers to apply custom configurations and styles.


## Styling and configuration

You can style system notifications with ``SwiftUI/View/systemNotificationStyle(_:)`` and ``SwiftUI/View/systemNotificationConfiguration(_:)``, which must be applied after the ``SwiftUI/View/systemNotification(_:)`` view modifier.
## How to create custom notification messages

You can style system notification message views with ``SwiftUI/View/systemNotificationMessageStyle(_:)``. This lets you style individual messages while keeping the global notification style for the corner radius, background material, etc.
The ``SystemNotificationMessage`` view lets you easily mimic a native notification message, with an icon, an optional title and a text, as well as an explicit style that overrides any environment style.

You can easily extend ``SystemNotificationMessage`` with your own custom messages, which can then be easily presented with the context's ``SystemNotificationContext/presentMessage(_:afterDelay:)`` function:

See ``SystemNotificationStyle``, ``SystemNotificationConfiguration`` and ``SystemNotificationMessageStyle`` for more information about how to style and configure these views.
```swift
extension SystemNotificationMessage where IconView == Image {

static var itemCreated: Self {
.init(
icon: Image(systemName: "checkmark"),
title: "Item created!",
text: "A new item was created",
style: ....
)
}
}
```

You can also use the ``SwiftUI/View/systemNotificationMessageStyle(_:)`` view modifier to provide a standard style for all other messages.
25 changes: 25 additions & 0 deletions Sources/SystemNotification/SystemNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,28 @@ private extension SystemNotification {

return MyView()
}


#Preview("README #3") {

struct MyView: View {

@State
var isSilentModeEnabled = false

@StateObject
var notification = SystemNotificationContext()

var body: some View {
List {
Toggle("Silent Mode", isOn: $isSilentModeEnabled)
}
.systemNotification(notification)
.onChange(of: isSilentModeEnabled) { value in
notification.presentMessage(.silentMode(on: value))
}
}
}

return MyView()
}
8 changes: 8 additions & 0 deletions Sources/SystemNotification/SystemNotificationContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ public class SystemNotificationContext: ObservableObject {
) {
present(content(), afterDelay: delay)
}

/// Present a system notification message.
public func presentMessage<IconType: View>(
_ message: SystemNotificationMessage<IconType>,
afterDelay delay: TimeInterval = 0
) {
present(message, afterDelay: delay)
}
}

private extension SystemNotificationContext {
Expand Down
165 changes: 165 additions & 0 deletions Sources/SystemNotification/SystemNotificationMessage+Predefined.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//
// SystemNotificationMessage+Predefined.swift
// SystemNotification
//
// Created by Daniel Saidi on 2024-04-24.
// Copyright © 2024 Daniel Saidi. All rights reserved.
//

import SwiftUI

extension SystemNotificationMessageStyle {

static var error: Self {
prominent(backgroundColor: .red)
}

static var success: Self {
prominent(backgroundColor: .green)
}

static var warning: Self {
prominent(backgroundColor: .orange)
}

static func prominent(
backgroundColor: Color
) -> Self {
.init(
backgroundColor: backgroundColor,
iconColor: .white,
textColor: .white.opacity(0.8),
titleColor: .white
)
}
}

extension SystemNotificationMessage where IconView == Image {

static func error(
icon: Image = .init(systemName: "exclamationmark.triangle"),
title: LocalizedStringKey? = nil,
text: LocalizedStringKey
) -> Self {
.init(
icon: icon,
title: title,
text: text,
style: .error
)
}

static func success(
icon: Image = .init(systemName: "checkmark"),
title: LocalizedStringKey? = nil,
text: LocalizedStringKey
) -> Self {
.init(
icon: icon,
title: title,
text: text,
style: .success
)
}

static func warning(
icon: Image = .init(systemName: "exclamationmark.triangle"),
title: LocalizedStringKey? = nil,
text: LocalizedStringKey
) -> Self {
.init(
icon: icon,
title: title,
text: text,
style: .warning
)
}
}

public extension SystemNotificationMessage where IconView == AnyView {

/// This message mimics a native iOS silent mode message.
static func silentMode(
on: Bool,
title: LocalizedStringKey? = nil
) -> Self {
.init(
icon: AnyView(SilentModeBell(isSilentModeOn: on)),
text: title ?? "Silent Mode \(on ? "On" : "Off")"
)
}
}

private struct SilentModeBell: View {

var isSilentModeOn = false

@State
private var isRotated: Bool = false

@State
private var isAnimated: Bool = false

var body: some View {
Image(systemName: iconName)
.rotationEffect(
.degrees(isRotated ? -45 : 0),
anchor: .top
)
.animation(
.interpolatingSpring(
mass: 0.5,
stiffness: animationStiffness,
damping: animationDamping,
initialVelocity: 0
),
value: isAnimated)
.foregroundColor(iconColor)
.onAppear(perform: animate)
}
}

private extension SilentModeBell {

func animate() {
withAnimation { isRotated = true }
perform(after: 0.1) {
isRotated = false
isAnimated = true
}
}

func perform(after: Double, action: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: action)
}

var animationDamping: Double {
isSilentModeOn ? 4 : 1.5
}

var animationStiffness: Double {
isSilentModeOn ? 129 : 179
}

var iconName: String {
isSilentModeOn ? "bell.slash.fill" : "bell.fill"
}

var iconColor: Color {
isSilentModeOn ? .red : .gray
}
}

#Preview {

VStack {
SystemNotificationMessage.silentMode(on: true)
SystemNotificationMessage.silentMode(on: false)
SystemNotificationMessage.error(title: "Error!", text: "Something failed!")
SystemNotificationMessage.success(title: "Success!", text: "You did it!")
SystemNotificationMessage.warning(title: "Warning!", text: "Danger ahead!")
}
.padding()
.background(Color.black.opacity(0.1))
.clipShape(.rect(cornerRadius: 10))
}
47 changes: 29 additions & 18 deletions Sources/SystemNotification/SystemNotificationMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ import SwiftUI
///
/// You can provide a custom icon view, title, and text, and
/// e.g. animate the icon when it's presented.
///
/// You can easily create custom messages, by extending this
/// type with static message builders, for instance:
///
/// ```swift
/// extension SystemNotificationMessage where IconView == Image {
///
/// static func silentMode(on: Bool) -> Self {
///
/// }
/// }
///
/// ```
public struct SystemNotificationMessage<IconView: View>: View {

/// Create a system notification message view.
Expand All @@ -21,15 +34,17 @@ public struct SystemNotificationMessage<IconView: View>: View {
/// - icon: The leading icon view.
/// - title: The bold title text, by default `nil`.
/// - text: The plain message text.
/// - style: An optional, explicit style to apply.
public init(
icon: IconView,
title: LocalizedStringKey? = nil,
text: LocalizedStringKey
text: LocalizedStringKey,
style: SystemNotificationMessageStyle? = nil
) {
self.icon = icon
self.title = title
self.text = text
self.initStyle = nil
self.initStyle = style
}

/// Create a system notification message view.
Expand All @@ -38,30 +53,34 @@ public struct SystemNotificationMessage<IconView: View>: View {
/// - icon: The leading icon image.
/// - title: The bold title text, by default `nil`.
/// - text: The plain message text.
/// - style: An optional, explicit style to apply.
public init(
icon: Image,
title: LocalizedStringKey? = nil,
text: LocalizedStringKey
text: LocalizedStringKey,
style: SystemNotificationMessageStyle? = nil
) where IconView == Image {
self.icon = icon
self.title = title
self.text = text
self.initStyle = nil
self.initStyle = style
}

/// Create a system notification message view.
///
/// - Parameters:
/// - title: The bold title text, by default `nil`.
/// - text: The plain message text.
/// - style: An optional, explicit style to apply.
public init(
title: LocalizedStringKey? = nil,
text: LocalizedStringKey
text: LocalizedStringKey,
style: SystemNotificationMessageStyle? = nil
) where IconView == EmptyView {
self.icon = EmptyView()
self.title = title
self.text = text
self.initStyle = nil
self.initStyle = style
}

let icon: IconView
Expand All @@ -70,7 +89,7 @@ public struct SystemNotificationMessage<IconView: View>: View {
let initStyle: SystemNotificationMessageStyle?

@Environment(\.systemNotificationMessageStyle)
private var envStyle
private var environmentStyle

public var body: some View {
HStack(spacing: style.iconTextSpacing) {
Expand All @@ -88,7 +107,7 @@ public struct SystemNotificationMessage<IconView: View>: View {
private extension SystemNotificationMessage {

var style: SystemNotificationMessageStyle {
initStyle ?? envStyle
initStyle ?? environmentStyle
}

func foregroundColor(
Expand Down Expand Up @@ -156,17 +175,9 @@ private extension SystemNotificationMessage {
SystemNotificationMessage(
icon: Image(systemName: "exclamationmark.triangle"),
title: "Warning",
text: "This is a long message to demonstrate multiline messages."
)
.systemNotificationMessageStyle(
.init(
iconColor: .orange,
iconFont: .headline,
textColor: .orange,
titleColor: .orange,
titleFont: .headline
)
text: "This is a long warning message to demonstrate multiline messages."
)
.systemNotificationMessageStyle(.warning)
}
.background(Color.white)
.cornerRadius(5)
Expand Down

0 comments on commit a35c4d1

Please sign in to comment.