diff --git a/Sources/OpenSwiftUICore/Data/Environment/AppearsActive.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/AppearsActive.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Environment/AppearsActive.swift rename to Sources/OpenSwiftUICore/Data/EnvironmentKeys/AppearsActive.swift diff --git a/Sources/OpenSwiftUICore/Data/Environment/Enabled.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/Enabled.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Environment/Enabled.swift rename to Sources/OpenSwiftUICore/Data/EnvironmentKeys/Enabled.swift diff --git a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentAdditions.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/EnvironmentAdditions.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Environment/EnvironmentAdditions.swift rename to Sources/OpenSwiftUICore/Data/EnvironmentKeys/EnvironmentAdditions.swift diff --git a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/IsVisionEnabled.swift similarity index 87% rename from Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift rename to Sources/OpenSwiftUICore/Data/EnvironmentKeys/IsVisionEnabled.swift index 3822e0a28..cbd5a176c 100644 --- a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift +++ b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/IsVisionEnabled.swift @@ -1,8 +1,8 @@ // -// EnvironmentValues+IsVisionEnabledKey.swift +// IsVisionEnabled.swift // OpenSwiftUICore // -// Audited for 6.0.87 +// Audited for 6.5.4 // Status: Complete extension EnvironmentValues { diff --git a/Sources/OpenSwiftUICore/Data/Environment/LuminanceReduced.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/LuminanceReduced.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Environment/LuminanceReduced.swift rename to Sources/OpenSwiftUICore/Data/EnvironmentKeys/LuminanceReduced.swift diff --git a/Sources/OpenSwiftUICore/Data/EnvironmentKeys/PrivacyRedaction.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/PrivacyRedaction.swift new file mode 100644 index 000000000..0c7ee15a8 --- /dev/null +++ b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/PrivacyRedaction.swift @@ -0,0 +1,224 @@ +// +// PrivacyRedaction.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: 7799685610985DBA9248562F2E4D5E6E (SwiftUICore) + +import OpenAttributeGraphShims +import OpenCoreGraphicsShims + +// MARK: - PrivacyRedactionViewModifier + +private struct PrivacyRedactionViewModifier: ViewModifier { + var sensitive: Bool + + nonisolated static func _makeView( + modifier: _GraphValue, + inputs: _ViewInputs, + body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs + ) -> _ViewOutputs { + let reasons = inputs.redactionReasons + let sensitive = modifier.unsafeBitCast(to: Bool.self).value + if inputs.hasWidgetMetadata { + let child = Attribute( + WidgetAuxiliaryChild(sensitive: sensitive, redactionReasons: reasons) + ) + return WidgetAuxiliaryChild.Value._makeView( + modifier: .init(child), + inputs: inputs, + body: body + ) + } else { + let provider = inputs.privacyReductionAccessibilityProvider + return makeChild( + modifier: modifier, + type: provider, + body: body, + sensitive: sensitive, + inputs: inputs, + reasons: reasons + ) + } + } + + nonisolated static func makeChild( + modifier: _GraphValue, + type: Provider.Type, + body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs, + sensitive: Attribute, + inputs: _ViewInputs, + reasons: Attribute + ) -> _ViewOutputs where Provider: PrivacyReductionAccessibilityProvider { + let child = Attribute( + Child(sensitive: sensitive, redactionReasons: reasons) + ) + return Child.Value.makeDebuggableView( + modifier: .init(child), + inputs: inputs, + body: body + ) + } + + struct Child: Rule where Provider: PrivacyReductionAccessibilityProvider { + @Attribute var sensitive: Bool + @Attribute var redactionReasons: RedactionReasons + + var value: some ViewModifier { + Transform(sensitive: sensitive, redactionReasons: redactionReasons) + } + } + + struct Transform: ViewModifier where Provider: PrivacyReductionAccessibilityProvider { + var sensitive: Bool + var redactionReasons: RedactionReasons + + @inline(__always) + private var shouldRedact: Bool { + redactionReasons.contains(.privacy) && sensitive + } + + func body(content: Content) -> some View { + content + .unredacted() + .modifier( + PrivacyEffect( + sensitive: sensitive, + shouldRedact: shouldRedact, + hideForScreencapture: redactionReasons.contains(.screencaptureProhibited) + ) + ) + .opacity(shouldRedact ? 0 : 1) + .modifier(Provider.makeModifier(shouldRedact: shouldRedact)) + .overlay { + if shouldRedact { + content + .environment(\.redactionReasons, .privacy) + .environment(\.sensitiveContent, sensitive) + .transition(.opacity) + } + } + } + + struct PrivacyEffect: RendererEffect { + var sensitive: Bool + var shouldRedact: Bool + var hideForScreencapture: Bool + + func effectValue(size: CGSize) -> DisplayList.Effect { + var properties: DisplayList.Properties = [] + if sensitive { + properties.formUnion(.privacySensitive) + } + if sensitive { + properties.formUnion(.screencaptureProhibited) + } + return .properties(properties) + } + + static var isScrapeable: Bool { true } + + var scrapeableContent: ScrapeableContent.Content? { + guard shouldRedact, hideForScreencapture else { + return nil + } + return .hidden + } + } + } + + struct WidgetAuxiliaryChild: Rule { + @Attribute var sensitive: Bool + @Attribute var redactionReasons: RedactionReasons + + var value: some ViewModifier { + let reasons: RedactionReasons = if redactionReasons.contains(.privacy), sensitive { + .privacy + } else { + redactionReasons + } + return ModifiedContent( + content: _EnvironmentKeyWritingModifier( + keyPath: \.redactionReasons, + value: reasons + ), + modifier: _EnvironmentKeyWritingModifier( + keyPath: \.sensitiveContent, + value: sensitive + ) + ) + } + } +} + +// MARK: - View + privacySensitive + +@available(OpenSwiftUI_v3_0, *) +extension View { + + /// Marks the view as containing sensitive, private user data. + /// + /// OpenSwiftUI redacts views marked with this modifier when you apply the + /// ``RedactionReasons/privacy`` redaction reason. + /// + /// struct BankAccountView: View { + /// var body: some View { + /// VStack { + /// Text("Account #") + /// + /// Text(accountNumber) + /// .font(.headline) + /// .privacySensitive() // Hide only the account number. + /// } + /// } + /// } + nonisolated public func privacySensitive(_ sensitive: Bool = true) -> some View { + modifier(PrivacyRedactionViewModifier(sensitive: sensitive)) + } +} + +// MARK: - PrivacyReductionAccessibilityProvider + +package protocol PrivacyReductionAccessibilityProvider { + associatedtype Modifier: ViewModifier + + static func makeModifier(shouldRedact: Bool) -> Modifier +} + +extension _GraphInputs { + private struct PrivacyReductionAccessibilityProviderKey: GraphInput { + static let defaultValue: (any PrivacyReductionAccessibilityProvider.Type) = EmptyPrivacyReductionAccessibilityProvider.self + } + + package var privacyReductionAccessibilityProvider: (any PrivacyReductionAccessibilityProvider.Type) { + get { self[PrivacyReductionAccessibilityProviderKey.self] } + set { self[PrivacyReductionAccessibilityProviderKey.self] = newValue } + } +} + +extension _ViewInputs { + package var privacyReductionAccessibilityProvider: (any PrivacyReductionAccessibilityProvider.Type) { + get { base.privacyReductionAccessibilityProvider } + set { base.privacyReductionAccessibilityProvider = newValue } + } +} + +struct EmptyPrivacyReductionAccessibilityProvider: PrivacyReductionAccessibilityProvider { + static func makeModifier(shouldRedact: Bool) -> some ViewModifier { + EmptyModifier() + } +} + +// MARK: - EnvironmentValues + sensitiveContent + +private struct SensitiveContentKey: EnvironmentKey { + static let defaultValue: Bool = false +} + +extension EnvironmentValues { + package var sensitiveContent: Bool { + get { self[SensitiveContentKey.self] } + set { self[SensitiveContentKey.self] = newValue } + } +} diff --git a/Sources/OpenSwiftUICore/Data/EnvironmentKeys/RedactionReasons.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/RedactionReasons.swift new file mode 100644 index 000000000..42a0090e3 --- /dev/null +++ b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/RedactionReasons.swift @@ -0,0 +1,176 @@ +// +// RedactionReasons.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Blocked by Image +// ID: 18671928047E57F039DC339288B6FAFB (SwiftUICore) + +import OpenAttributeGraphShims + +// MARK: - RedactionReasons + +/// The reasons to apply a redaction to data displayed on screen. +@available(OpenSwiftUI_v2_0, *) +public struct RedactionReasons: OptionSet, Sendable { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + /// Displayed data should appear as generic placeholders. + /// + /// Text and images will be automatically masked to appear as + /// generic placeholders, though maintaining their original size and shape. + /// Use this to create a placeholder UI without directly exposing + /// placeholder data to users. + public static let placeholder: RedactionReasons = .init(rawValue: 1 << 0) + + /// Displayed data should be obscured to protect private information. + /// + /// Views marked with `privacySensitive` will be automatically redacted + /// using a standard styling. To apply a custom treatment the redaction + /// reason can be read out of the environment. + /// + /// struct BankingContentView: View { + /// @Environment(\.redactionReasons) var redactionReasons + /// + /// var body: some View { + /// if redactionReasons.contains(.privacy) { + /// FullAppCover() + /// } else { + /// AppContent() + /// } + /// } + /// } + @available(OpenSwiftUI_v3_0, *) + public static let privacy: RedactionReasons = .init(rawValue: 1 << 1) + + /// Displayed data should appear as invalidated and pending a new update. + /// + /// Views marked with `invalidatableContent` will be automatically + /// redacted with a standard styling indicating the content is invalidated + /// and new content will be available soon. + @available(OpenSwiftUI_v5_0, *) + public static let invalidated: RedactionReasons = .init(rawValue: 1 << 2) + + @_spi(Private) + @available(OpenSwiftUI_v6_0, *) + public static let screencaptureProhibited: RedactionReasons = .init(rawValue: 1 << 3) +} + +// MARK: - View + redacted + +@available(OpenSwiftUI_v2_0, *) +extension View { + + /// Adds a reason to apply a redaction to this view hierarchy. + /// + /// Adding a redaction is an additive process: any redaction + /// provided will be added to the reasons provided by the parent. + nonisolated public func redacted(reason: RedactionReasons) -> some View { + transformEnvironment(\.redactionReasons) { + $0.insert(reason) + } + } + + /// Removes any reason to apply a redaction to this view hierarchy. + nonisolated public func unredacted() -> some View { + environment(\.redactionReasons, []) + } +} + +// MARK: - EnvironmentValues + RedactionReasons + +private struct RedactionReasonsKey: EnvironmentKey { + static let defaultValue: RedactionReasons = [] +} + +private struct ShouldRedactContentKey: DerivedEnvironmentKey { + static func value(in environment: EnvironmentValues) -> Bool { + let redactionReasons = environment.redactionReasons + if redactionReasons.contains(.placeholder) { + return true + } else if redactionReasons.contains(.privacy) { + return environment.sensitiveContent + } else { + return false + } + } +} + +private struct UnredactSymbolImage: EnvironmentKey { + static let defaultValue: Bool = false +} + +private struct ShouldRedactSymbolImagesKey: DerivedEnvironmentKey { + static func value(in environment: EnvironmentValues) -> Bool { + guard environment.shouldRedactContent else { + return false + } + return !environment.unredactSymbolImage + } +} + +extension EnvironmentValues { + package var shouldRedactContent: Bool { + self[ShouldRedactContentKey.self] + } +} + +@_spi(Private) +@available(OpenSwiftUI_v6_0, *) +extension EnvironmentValues { + + public var unredactSymbolImage: Bool { + get { self[UnredactSymbolImage.self] } + set { self[UnredactSymbolImage.self] = newValue } + } + + package var shouldRedactSymbolImages: Bool { + self[ShouldRedactSymbolImagesKey.self] + } +} + +@available(OpenSwiftUI_v2_0, *) +extension EnvironmentValues { + + /// The current redaction reasons applied to the view hierarchy. + public var redactionReasons: RedactionReasons { + get { self[RedactionReasonsKey.self] } + set { self[RedactionReasonsKey.self] = newValue } + } +} + +extension CachedEnvironment.ID { + static let redactionReasons: CachedEnvironment.ID = .init() +} + +extension _GraphInputs { + @inline(__always) + var redactionReasons: Attribute { + mapEnvironment(id: .redactionReasons) { $0.redactionReasons } + } +} + +extension _ViewInputs { + @inline(__always) + var redactionReasons: Attribute { + base.redactionReasons + } +} + +// MARK: - Image + redacted [TODO] + +extension GraphicsImage { + package mutating func redact(in environment: EnvironmentValues) { + _openSwiftUIUnimplementedFailure() + } +} + +extension Image { + package static let redacted: Image = { + _openSwiftUIUnimplementedFailure() + }() +} diff --git a/Sources/OpenSwiftUICore/Data/Environment/WindowEnvironment.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/WindowEnvironment.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Environment/WindowEnvironment.swift rename to Sources/OpenSwiftUICore/Data/EnvironmentKeys/WindowEnvironment.swift diff --git a/Sources/OpenSwiftUICore/Modifier/ViewModifier/RedactionReasons.swift b/Sources/OpenSwiftUICore/Modifier/ViewModifier/RedactionReasons.swift deleted file mode 100644 index 3fb7476af..000000000 --- a/Sources/OpenSwiftUICore/Modifier/ViewModifier/RedactionReasons.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// RedactionReasons.swift -// OpenSwiftUICore -// -// Audited for 6.5.4 -// Status: WIP -// ID: 18671928047E57F039DC339288B6FAFB - - -// MARK: - EnvironmentValues + RedactionReasons - -// TODO -private struct ShouldRedactContentKey: DerivedEnvironmentKey { - static func value(in environment: EnvironmentValues) -> Bool { - // redaction - false - } -} - -extension EnvironmentValues { - package var shouldRedactContent: Bool { - self[ShouldRedactContentKey.self] - } -} diff --git a/Sources/OpenSwiftUICore/Render/OpacityEffect.swift b/Sources/OpenSwiftUICore/Render/OpacityEffect.swift index 8b78b0d64..9bb9e23b5 100644 --- a/Sources/OpenSwiftUICore/Render/OpacityEffect.swift +++ b/Sources/OpenSwiftUICore/Render/OpacityEffect.swift @@ -32,6 +32,12 @@ public struct _OpacityEffect: RendererEffect, Equatable { .opacity(Float(opacity)) } + package static var isScrapeable: Bool { true } + + package var scrapeableContent: ScrapeableContent.Content? { + .opacity(opacity) + } + nonisolated public static func _makeView( modifier: _GraphValue, inputs: _ViewInputs, diff --git a/Sources/OpenSwiftUICore/Render/RendererEffect/RendererEffect.swift b/Sources/OpenSwiftUICore/Render/RendererEffect/RendererEffect.swift index 4ad98fcf2..f455009f8 100644 --- a/Sources/OpenSwiftUICore/Render/RendererEffect/RendererEffect.swift +++ b/Sources/OpenSwiftUICore/Render/RendererEffect/RendererEffect.swift @@ -28,25 +28,15 @@ package protocol _RendererEffect: MultiViewModifier, PrimitiveViewModifier { // MARK: - _RendererEffect + Default Implementation extension _RendererEffect { - package static var isolatesChildPosition: Bool { - false - } + package static var isolatesChildPosition: Bool { false } - package static var disabledForFlattenedContent: Bool { - false - } + package static var disabledForFlattenedContent: Bool { false } - package static var preservesEmptyContent: Bool { - false - } + package static var preservesEmptyContent: Bool { false } - package static var isScrapeable: Bool { - false - } + package static var isScrapeable: Bool { false } - package var scrapeableContent: ScrapeableContent.Content? { - nil - } + package var scrapeableContent: ScrapeableContent.Content? { nil } package static func _makeRendererEffect( effect: _GraphValue, diff --git a/Sources/OpenSwiftUICore/View/Image/Image.swift b/Sources/OpenSwiftUICore/View/Image/Image.swift index a0e6cf60d..5dae0bab9 100644 --- a/Sources/OpenSwiftUICore/View/Image/Image.swift +++ b/Sources/OpenSwiftUICore/View/Image/Image.swift @@ -11,17 +11,17 @@ public import CoreGraphics @frozen public struct Image: Equatable, Sendable { - // FIXME - package static var redacted: Image { - _openSwiftUIUnimplementedFailure() - } - #if canImport(CoreGraphics) // FIXME public init(decorative: CGImage, scale: CGFloat, orientation: Image.Orientation) { _openSwiftUIUnimplementedFailure() } #endif + + // FIXME + package enum Resolved {} + + package enum NamedResolved {} }