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 68c76b2
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 24 deletions.
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 68c76b2

Please sign in to comment.