Skip to content

Commit

Permalink
feat: [JIRA: HCPSDKFIORIUIKIT-2488] SwiftUI InformationView (Validati…
Browse files Browse the repository at this point in the history
…onView) (#636)

* feat: [JIRA: HCPSDKFIORIUIKIT-2488] SwiftUI InformationView (ValidationView)

* feat: [JIRA: HCPSDKFIORIUIKIT-2488] SwiftUI InformationView (ValidationView)

* feat: [JIRA: HCPSDKFIORIUIKIT-2488] SwiftUI InformationView (ValidationView)

* feat: [JIRA: HCPSDKFIORIUIKIT-2488] SwiftUI InformationView (ValidationView)

* feat: 🎸 [JIRA: HCPSDKFIORIUIKIT-2488] SwiftUI InformationView

SwiftUI InformationView (ValidationView)

✅ Closes: JIRA: HCPSDKFIORIUIKIT-2488

* feat: 🎸 [JIRA: HCPSDKFIORIUIKIT-2488] SwiftUI InformationView

feat: [JIRA: HCPSDKFIORIUIKIT-2488] SwiftUI InformationView
(ValidationView)

✅ Closes: JIRA: HCPSDKFIORIUIKIT-2488

---------

Co-authored-by: I824136 <xiaoqing.he@sap.com>
Co-authored-by: Bill Zhou <bill.zhou01@sap.com>
  • Loading branch information
3 people committed Feb 17, 2024
1 parent 32651ab commit 96b6220
Show file tree
Hide file tree
Showing 17 changed files with 689 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Apps/Examples/Examples.xcodeproj/project.pbxproj
Expand Up @@ -60,6 +60,7 @@
8AD9DFB325D49967007448EC /* ContactItemInitModelExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD9DFA825D49967007448EC /* ContactItemInitModelExample.swift */; };
8AD9DFB425D49967007448EC /* ContactItemInitViewBuilderExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD9DFA925D49967007448EC /* ContactItemInitViewBuilderExample.swift */; };
975CB76B256C5A7400DB7A15 /* SignatureCaptureViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 975CB76A256C5A7400DB7A15 /* SignatureCaptureViewExample.swift */; };
99193C852B719B8800F33BAF /* InformationViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99193C842B719B8800F33BAF /* InformationViewExample.swift */; };
993A122E26A726AC0018028B /* PrivacyText.html in Resources */ = {isa = PBXBuildFile; fileRef = 993A122C26A726AC0018028B /* PrivacyText.html */; };
993A122F26A726AC0018028B /* PrivacyText.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 993A122D26A726AC0018028B /* PrivacyText.rtf */; };
993B55BE29DF7EC70002B065 /* IconLibraryExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993B55BD29DF7EC70002B065 /* IconLibraryExample.swift */; };
Expand Down Expand Up @@ -218,6 +219,7 @@
8AD9DFA825D49967007448EC /* ContactItemInitModelExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactItemInitModelExample.swift; sourceTree = "<group>"; };
8AD9DFA925D49967007448EC /* ContactItemInitViewBuilderExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactItemInitViewBuilderExample.swift; sourceTree = "<group>"; };
975CB76A256C5A7400DB7A15 /* SignatureCaptureViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureCaptureViewExample.swift; sourceTree = "<group>"; };
99193C842B719B8800F33BAF /* InformationViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InformationViewExample.swift; sourceTree = "<group>"; };
993A122C26A726AC0018028B /* PrivacyText.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = PrivacyText.html; sourceTree = "<group>"; };
993A122D26A726AC0018028B /* PrivacyText.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = PrivacyText.rtf; sourceTree = "<group>"; };
993B55BD29DF7EC70002B065 /* IconLibraryExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconLibraryExample.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -311,6 +313,7 @@
children = (
1F3C92F025DF12C100A99A07 /* ListPicker.swift */,
B141D6BA29261F9E008A8BD6 /* SearchableListViewExample.swift */,
99193C842B719B8800F33BAF /* InformationViewExample.swift */,
);
path = FormCells;
sourceTree = "<group>";
Expand Down Expand Up @@ -851,6 +854,7 @@
C106AD442B33710800FE8B35 /* SearchWithScope.swift in Sources */,
B1C7DC8129FBB13F00DC5EEB /* SPIModelExample.swift in Sources */,
C106AD462B338D1300FE8B35 /* SearchWithToken.swift in Sources */,
99193C852B719B8800F33BAF /* InformationViewExample.swift in Sources */,
8A5579D824C1293C0098003A /* SettingsIndexSet.swift in Sources */,
8A55795924C1286E0098003A /* SceneDelegate.swift in Sources */,
1FF3662E264C662A00AB8BD8 /* DimensionSelector+Chart.swift in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift
Expand Up @@ -132,6 +132,13 @@ struct CoreContentView: View {
NavigationLink(destination: SearchDemos()) {
Text("Search Demos")
}

NavigationLink(
destination: InformationViewExample(),
label: {
Text("InformationViewExample")
}
)
}
}.navigationBarTitle("FioriSwiftUICore")
}
Expand Down
@@ -0,0 +1,45 @@
import FioriSwiftUICore
import SwiftUI

struct InformationViewExample: View {
var body: some View {
VStack(alignment: .leading) {
Text("Default Fiori style, no icon")
.informationView(description: AttributedString("test message"))

Text("Error style")
.informationView(description: AttributedString("test message"))
.informationViewStyle(.error)

Text("Warning style")
.informationView(description: AttributedString("test message"))
.informationViewStyle(.warning)

Text("Informational style")
.informationView(description: AttributedString("test message"))
.informationViewStyle(.informational)

Text("Success style")
.informationView(description: AttributedString("test message"))
.informationViewStyle(.success)

Text("Customized icon")
.informationView(icon: Image(systemName: "heart"), description: AttributedString("test message"))

Text("Customized font and color")
.informationView(icon: Image(systemName: "diamond"), description: AttributedString("test message"))
.informationViewStyle(.fiori)
.iconStyle(content: { iconConfiguration in
iconConfiguration.icon
.foregroundStyle(Color.preferredColor(.tintColor))
})
.descriptionStyle(content: { descriptionConfiguration in
descriptionConfiguration.description
.foregroundStyle(Color.preferredColor(.criticalLabel))
.font(.fiori(forTextStyle: .title2))
})

Spacer()
}
}
}
Expand Up @@ -71,6 +71,12 @@ protocol _AvatarsComponent {
var avatars: [TextOrIcon] { get }
}

// sourcery: BaseComponent
protocol _IconComponent {
// sourcery: @ViewBuilder
var icon: Image? { get }
}

// TODO: rename to _ActionComponent after resolving the conflict.
// sourcery: BaseComponent
protocol _ActionComponent {
Expand Down
Expand Up @@ -25,3 +25,9 @@ protocol _CardHeaderComponent: _CardMediaComponent, _CardMainHeaderComponent, _C

// sourcery: CompositeComponent
protocol _CardComponent: _CardHeaderComponent, _CardBodyComponent, _CardFooterComponent {}

// sourcery: CompositeComponent
protocol _InformationViewComponent: _IconComponent, _DescriptionComponent {
@ViewBuilder
var content: () -> any View { get }
}
19 changes: 19 additions & 0 deletions Sources/FioriSwiftUICore/_FioriStyles/IconStyle.fiori.swift
@@ -0,0 +1,19 @@
import FioriThemeManager
import Foundation
import SwiftUI

// Base Layout style
public struct IconBaseStyle: IconStyle {
@ViewBuilder
public func makeBody(_ configuration: IconConfiguration) -> some View {
configuration.icon
}
}

// Default fiori styles
public struct IconFioriStyle: IconStyle {
@ViewBuilder
public func makeBody(_ configuration: IconConfiguration) -> some View {
Icon(configuration)
}
}
158 changes: 158 additions & 0 deletions Sources/FioriSwiftUICore/_FioriStyles/InformationViewStyle.fiori.swift
@@ -0,0 +1,158 @@
import FioriThemeManager
import Foundation
import SwiftUI

// Base Layout style
public struct InformationViewBaseStyle: InformationViewStyle {
public func makeBody(_ configuration: InformationViewConfiguration) -> some View {
VStack {
configuration.content
HStack(alignment: .center, spacing: 8) {
configuration.icon
configuration.description
Spacer()
}
}
}
}

// Default fiori styles
extension InformationViewFioriStyle {
struct ContentFioriStyle: InformationViewStyle {
func makeBody(_ configuration: InformationViewConfiguration) -> some View {
InformationView(configuration)
.foregroundColor(.preferredColor(.primaryLabel))
.padding(.leading, 16)
.padding(.trailing, 16)
.padding(.top, 4)
.padding(.bottom, 11)
}
}

struct IconFioriStyle: IconStyle {
func makeBody(_ configuration: IconConfiguration) -> some View {
Icon(configuration)
.foregroundStyle(Color.preferredColor(.primaryLabel))
.font(.fiori(forTextStyle: .footnote))
}
}

struct DescriptionFioriStyle: DescriptionStyle {
func makeBody(_ configuration: DescriptionConfiguration) -> some View {
Description(configuration)
.foregroundStyle(Color.preferredColor(.primaryLabel))
.font(.fiori(forTextStyle: .footnote))
}
}
}

public extension View {
/// To show the InformationView at the bottom of the view. It includes an icon and text. It is used in error handling to show error / warning / informational / success confirmation message.
func informationView(icon: Image? = nil, description: AttributedString) -> some View {
InformationView(icon: icon, description: description, content: { self })
}
}

public struct InformationViewErrorStyle: InformationViewStyle {
public func makeBody(_ configuration: InformationViewConfiguration) -> some View {
InformationView(configuration)
.iconStyle(content: { IconConfiguration in
if IconConfiguration.icon.isEmpty {
Image(systemName: "exclamationmark.circle")
.foregroundStyle(Color.preferredColor(.negativeLabel))
} else {
IconConfiguration.icon
.foregroundStyle(Color.preferredColor(.negativeLabel))
}
})
.descriptionStyle(content: { descriptionConfiguration in
descriptionConfiguration.description
.foregroundStyle(Color.preferredColor(.negativeLabel))
})
}
}

public struct InformationViewWarningStyle: InformationViewStyle {
public func makeBody(_ configuration: InformationViewConfiguration) -> some View {
InformationView(configuration)
.iconStyle(content: { IconConfiguration in
if IconConfiguration.icon.isEmpty {
Image(systemName: "exclamationmark.triangle")
.foregroundStyle(Color.preferredColor(.mango5))
} else {
IconConfiguration.icon
.foregroundStyle(Color.preferredColor(.mango5))
}
})
.descriptionStyle(content: { descriptionConfiguration in
descriptionConfiguration.description
.foregroundStyle(Color.preferredColor(.mango5))
})
}
}

public struct InformationViewInformationalStyle: InformationViewStyle {
public func makeBody(_ configuration: InformationViewConfiguration) -> some View {
InformationView(configuration)
.iconStyle(content: { IconConfiguration in
if IconConfiguration.icon.isEmpty {
Image(systemName: "info.circle")
.foregroundStyle(Color.preferredColor(.primaryLabel))
} else {
IconConfiguration.icon
.foregroundStyle(Color.preferredColor(.primaryLabel))
}
})
.descriptionStyle(content: { descriptionConfiguration in
descriptionConfiguration.description
.foregroundStyle(Color.preferredColor(.primaryLabel))
})
}
}

public struct InformationViewSuccessStyle: InformationViewStyle {
public func makeBody(_ configuration: InformationViewConfiguration) -> some View {
InformationView(configuration)
.iconStyle(content: { IconConfiguration in
if IconConfiguration.icon.isEmpty {
Image(systemName: "checkmark.circle")
.foregroundStyle(Color.preferredColor(.positiveLabel))
} else {
IconConfiguration.icon
.foregroundStyle(Color.preferredColor(.positiveLabel))
}
})
.descriptionStyle(content: { descriptionConfiguration in
descriptionConfiguration.description
.foregroundStyle(Color.preferredColor(.positiveLabel))
})
}
}

/// Error style of the InformationView. It is used to show error message.
public extension InformationViewStyle where Self == InformationViewErrorStyle {
static var error: InformationViewErrorStyle {
InformationViewErrorStyle()
}
}

/// Warning style of the InformationView. It is used to show warning message.
public extension InformationViewStyle where Self == InformationViewWarningStyle {
static var warning: InformationViewWarningStyle {
InformationViewWarningStyle()
}
}

/// Informationalstyle of the InformationView. It is used to show informational message.
public extension InformationViewStyle where Self == InformationViewInformationalStyle {
static var informational: InformationViewInformationalStyle {
InformationViewInformationalStyle()
}
}

/// Success style of the InformationView. It is used to show success message.
public extension InformationViewStyle where Self == InformationViewSuccessStyle {
static var success: InformationViewSuccessStyle {
InformationViewSuccessStyle()
}
}
@@ -0,0 +1,58 @@
// Generated using Sourcery 2.1.3 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
import Foundation
import SwiftUI

public struct Icon {
let icon: any View

@Environment(\.iconStyle) var style

fileprivate var _shouldApplyDefaultStyle = true

public init(@ViewBuilder icon: () -> any View = { EmptyView() }) {
self.icon = icon()
}
}

public extension Icon {
init(icon: Image? = nil) {
self.init(icon: { icon })
}
}

public extension Icon {
init(_ configuration: IconConfiguration) {
self.icon = configuration.icon
self._shouldApplyDefaultStyle = false
}
}

extension Icon: View {
public var body: some View {
if self._shouldApplyDefaultStyle {
self.defaultStyle()
} else {
self.style.resolve(configuration: .init(icon: .init(self.icon))).typeErased
.transformEnvironment(\.iconStyleStack) { stack in
if !stack.isEmpty {
stack.removeLast()
}
}
}
}
}

private extension Icon {
func shouldApplyDefaultStyle(_ bool: Bool) -> some View {
var s = self
s._shouldApplyDefaultStyle = bool
return s
}

func defaultStyle() -> some View {
Icon(icon: { self.icon })
.shouldApplyDefaultStyle(false)
.iconStyle(.fiori)
}
}
@@ -0,0 +1,28 @@
// Generated using Sourcery 2.1.3 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
import Foundation
import SwiftUI

public protocol IconStyle: DynamicProperty {
associatedtype Body: View

func makeBody(_ configuration: IconConfiguration) -> Body
}

struct AnyIconStyle: IconStyle {
let content: (IconConfiguration) -> any View

init(@ViewBuilder _ content: @escaping (IconConfiguration) -> any View) {
self.content = content
}

public func makeBody(_ configuration: IconConfiguration) -> some View {
self.content(configuration).typeErased
}
}

public struct IconConfiguration {
public let icon: Icon

public typealias Icon = ConfigurationViewWrapper
}

0 comments on commit 96b6220

Please sign in to comment.