Skip to content

Commit 5970710

Browse files
committed
Implement PrivacyRedaction
1 parent 3b79c7f commit 5970710

File tree

3 files changed

+244
-18
lines changed

3 files changed

+244
-18
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
//
2+
// PrivacyRedaction.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
// ID: 7799685610985DBA9248562F2E4D5E6E (SwiftUICore)
8+
9+
import OpenAttributeGraphShims
10+
import OpenCoreGraphicsShims
11+
12+
// MARK: - PrivacyRedactionViewModifier
13+
14+
private struct PrivacyRedactionViewModifier: ViewModifier {
15+
var sensitive: Bool
16+
17+
nonisolated static func _makeView(
18+
modifier: _GraphValue<Self>,
19+
inputs: _ViewInputs,
20+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
21+
) -> _ViewOutputs {
22+
let reasons = inputs.redactionReasons
23+
let sensitive = modifier.unsafeBitCast(to: Bool.self).value
24+
if inputs.hasWidgetMetadata {
25+
let child = Attribute(
26+
WidgetAuxiliaryChild(sensitive: sensitive, redactionReasons: reasons)
27+
)
28+
return WidgetAuxiliaryChild.Value._makeView(
29+
modifier: .init(child),
30+
inputs: inputs,
31+
body: body
32+
)
33+
} else {
34+
let provider = inputs.privacyReductionAccessibilityProvider
35+
return makeChild(
36+
modifier: modifier,
37+
type: provider,
38+
body: body,
39+
sensitive: sensitive,
40+
inputs: inputs,
41+
reasons: reasons
42+
)
43+
}
44+
}
45+
46+
nonisolated static func makeChild<Provider>(
47+
modifier: _GraphValue<Self>,
48+
type: Provider.Type,
49+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs,
50+
sensitive: Attribute<Bool>,
51+
inputs: _ViewInputs,
52+
reasons: Attribute<RedactionReasons>
53+
) -> _ViewOutputs where Provider: PrivacyReductionAccessibilityProvider {
54+
let child = Attribute(
55+
Child<Provider>(sensitive: sensitive, redactionReasons: reasons)
56+
)
57+
return Child<Provider>.Value.makeDebuggableView(
58+
modifier: .init(child),
59+
inputs: inputs,
60+
body: body
61+
)
62+
}
63+
64+
struct Child<Provider>: Rule where Provider: PrivacyReductionAccessibilityProvider {
65+
@Attribute var sensitive: Bool
66+
@Attribute var redactionReasons: RedactionReasons
67+
68+
var value: some ViewModifier {
69+
Transform<Provider>(sensitive: sensitive, redactionReasons: redactionReasons)
70+
}
71+
}
72+
73+
struct Transform<Provider>: ViewModifier where Provider: PrivacyReductionAccessibilityProvider {
74+
var sensitive: Bool
75+
var redactionReasons: RedactionReasons
76+
77+
@inline(__always)
78+
private var shouldRedact: Bool {
79+
redactionReasons.contains(.privacy) && sensitive
80+
}
81+
82+
func body(content: Content) -> some View {
83+
content
84+
.unredacted()
85+
.modifier(
86+
PrivacyEffect(
87+
sensitive: sensitive,
88+
shouldRedact: shouldRedact,
89+
hideForScreencapture: redactionReasons.contains(.screencaptureProhibited)
90+
)
91+
)
92+
.opacity(shouldRedact ? 0 : 1)
93+
.modifier(Provider.makeModifier(shouldRedact: shouldRedact))
94+
.overlay {
95+
if shouldRedact {
96+
content
97+
.environment(\.redactionReasons, .privacy)
98+
.environment(\.sensitiveContent, sensitive)
99+
.transition(.opacity)
100+
}
101+
}
102+
}
103+
104+
struct PrivacyEffect: RendererEffect {
105+
var sensitive: Bool
106+
var shouldRedact: Bool
107+
var hideForScreencapture: Bool
108+
109+
func effectValue(size: CGSize) -> DisplayList.Effect {
110+
var properties: DisplayList.Properties = []
111+
if sensitive {
112+
properties.formUnion(.privacySensitive)
113+
}
114+
if sensitive {
115+
properties.formUnion(.screencaptureProhibited)
116+
}
117+
return .properties(properties)
118+
}
119+
120+
static var isScrapeable: Bool { true }
121+
122+
var scrapeableContent: ScrapeableContent.Content? {
123+
guard shouldRedact, hideForScreencapture else {
124+
return nil
125+
}
126+
return .hidden
127+
}
128+
}
129+
}
130+
131+
struct WidgetAuxiliaryChild: Rule {
132+
@Attribute var sensitive: Bool
133+
@Attribute var redactionReasons: RedactionReasons
134+
135+
var value: some ViewModifier {
136+
let reasons: RedactionReasons = if redactionReasons.contains(.privacy), sensitive {
137+
.privacy
138+
} else {
139+
redactionReasons
140+
}
141+
return ModifiedContent(
142+
content: _EnvironmentKeyWritingModifier(
143+
keyPath: \.redactionReasons,
144+
value: reasons
145+
),
146+
modifier: _EnvironmentKeyWritingModifier(
147+
keyPath: \.sensitiveContent,
148+
value: sensitive
149+
)
150+
)
151+
}
152+
}
153+
}
154+
155+
// MARK: - View + privacySensitive
156+
157+
@available(OpenSwiftUI_v3_0, *)
158+
extension View {
159+
160+
/// Marks the view as containing sensitive, private user data.
161+
///
162+
/// OpenSwiftUI redacts views marked with this modifier when you apply the
163+
/// ``RedactionReasons/privacy`` redaction reason.
164+
///
165+
/// struct BankAccountView: View {
166+
/// var body: some View {
167+
/// VStack {
168+
/// Text("Account #")
169+
///
170+
/// Text(accountNumber)
171+
/// .font(.headline)
172+
/// .privacySensitive() // Hide only the account number.
173+
/// }
174+
/// }
175+
/// }
176+
nonisolated public func privacySensitive(_ sensitive: Bool = true) -> some View {
177+
modifier(PrivacyRedactionViewModifier(sensitive: sensitive))
178+
}
179+
}
180+
181+
// MARK: - PrivacyReductionAccessibilityProvider
182+
183+
package protocol PrivacyReductionAccessibilityProvider {
184+
associatedtype Modifier: ViewModifier
185+
186+
static func makeModifier(shouldRedact: Bool) -> Modifier
187+
}
188+
189+
extension _GraphInputs {
190+
private struct PrivacyReductionAccessibilityProviderKey: GraphInput {
191+
static let defaultValue: (any PrivacyReductionAccessibilityProvider.Type) = EmptyPrivacyReductionAccessibilityProvider.self
192+
}
193+
194+
package var privacyReductionAccessibilityProvider: (any PrivacyReductionAccessibilityProvider.Type) {
195+
get { self[PrivacyReductionAccessibilityProviderKey.self] }
196+
set { self[PrivacyReductionAccessibilityProviderKey.self] = newValue }
197+
}
198+
}
199+
200+
extension _ViewInputs {
201+
package var privacyReductionAccessibilityProvider: (any PrivacyReductionAccessibilityProvider.Type) {
202+
get { base.privacyReductionAccessibilityProvider }
203+
set { base.privacyReductionAccessibilityProvider = newValue }
204+
}
205+
}
206+
207+
struct EmptyPrivacyReductionAccessibilityProvider: PrivacyReductionAccessibilityProvider {
208+
static func makeModifier(shouldRedact: Bool) -> some ViewModifier {
209+
EmptyModifier()
210+
}
211+
}
212+
213+
// MARK: - EnvironmentValues + sensitiveContent
214+
215+
private struct SensitiveContentKey: EnvironmentKey {
216+
static let defaultValue: Bool = false
217+
}
218+
219+
extension EnvironmentValues {
220+
package var sensitiveContent: Bool {
221+
get { self[SensitiveContentKey.self] }
222+
set { self[SensitiveContentKey.self] = newValue }
223+
}
224+
}

Sources/OpenSwiftUICore/Data/EnvironmentKeys/RedactionReasons.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
// Status: Blocked by Image
77
// ID: 18671928047E57F039DC339288B6FAFB (SwiftUICore)
88

9+
import OpenAttributeGraphShims
10+
911
// MARK: - RedactionReasons
1012

1113
/// The reasons to apply a redaction to data displayed on screen.
@@ -141,6 +143,24 @@ extension EnvironmentValues {
141143
}
142144
}
143145

146+
extension CachedEnvironment.ID {
147+
static let redactionReasons: CachedEnvironment.ID = .init()
148+
}
149+
150+
extension _GraphInputs {
151+
@inline(__always)
152+
var redactionReasons: Attribute<RedactionReasons> {
153+
mapEnvironment(id: .redactionReasons) { $0.redactionReasons }
154+
}
155+
}
156+
157+
extension _ViewInputs {
158+
@inline(__always)
159+
var redactionReasons: Attribute<RedactionReasons> {
160+
base.redactionReasons
161+
}
162+
}
163+
144164
// MARK: - Image + redacted [TODO]
145165

146166
extension GraphicsImage {

Sources/OpenSwiftUICore/Data/EnvironmentKeys/SensitiveContent.swift

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)