diff --git a/Sources/TokamakCore/App/App.swift b/Sources/TokamakCore/App/App.swift index 82aeccba4..daedc269f 100644 --- a/Sources/TokamakCore/App/App.swift +++ b/Sources/TokamakCore/App/App.swift @@ -32,7 +32,10 @@ public protocol App: _TitledApp { static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues) /// Implemented by the renderer to update the `App` on `ScenePhase` changes - var _phasePublisher: CurrentValueSubject { get } + var _phasePublisher: AnyPublisher { get } + + /// Implemented by the renderer to update the `App` on `ColorScheme` changes + var _colorSchemePublisher: AnyPublisher { get } static func main() diff --git a/Sources/TokamakCore/App/_AnyApp.swift b/Sources/TokamakCore/App/_AnyApp.swift index 0c0e80b96..07a75f1be 100644 --- a/Sources/TokamakCore/App/_AnyApp.swift +++ b/Sources/TokamakCore/App/_AnyApp.swift @@ -48,7 +48,11 @@ public struct _AnyApp: App { fatalError("`title` cannot be set for `AnyApp`. Access underlying `app` value.") } - public var _phasePublisher: CurrentValueSubject { + public var _phasePublisher: AnyPublisher { fatalError("`_AnyApp` cannot monitor scenePhase. Access underlying `app` value.") } + + public var _colorSchemePublisher: AnyPublisher { + fatalError("`_AnyApp` cannot monitor colorScheme. Access underlying `app` value.") + } } diff --git a/Sources/TokamakCore/Environment/Environment.swift b/Sources/TokamakCore/Environment/Environment.swift index a4064eb50..7e79f8d52 100644 --- a/Sources/TokamakCore/Environment/Environment.swift +++ b/Sources/TokamakCore/Environment/Environment.swift @@ -29,8 +29,8 @@ protocol EnvironmentReader { case value(Value) } - var content: Content - let keyPath: KeyPath + private var content: Content + private let keyPath: KeyPath public init(_ keyPath: KeyPath) { content = .keyPath(keyPath) self.keyPath = keyPath diff --git a/Sources/TokamakCore/MountedViews/MountedHostView.swift b/Sources/TokamakCore/MountedViews/MountedHostView.swift index 071643835..cabfa7772 100644 --- a/Sources/TokamakCore/MountedViews/MountedHostView.swift +++ b/Sources/TokamakCore/MountedViews/MountedHostView.swift @@ -94,6 +94,7 @@ public final class MountedHostView: MountedElement { while let child = mountedChildren.first, let firstChild = childrenViews.first { let newChild: MountedElement if firstChild.typeConstructorName == mountedChildren[0].view.typeConstructorName { + child.environmentValues = environmentValues child.view = firstChild child.updateEnvironment() child.update(with: reconciler) diff --git a/Sources/TokamakCore/StackReconciler.swift b/Sources/TokamakCore/StackReconciler.swift index 284b6b756..7b6b88204 100644 --- a/Sources/TokamakCore/StackReconciler.swift +++ b/Sources/TokamakCore/StackReconciler.swift @@ -94,12 +94,8 @@ public final class StackReconciler { rootElement.mount(with: self) if let mountedApp = rootElement as? MountedApp { - app._phasePublisher.sink { [weak self] phase in - if mountedApp.environmentValues.scenePhase != phase { - mountedApp.environmentValues.scenePhase = phase - self?.queueUpdate(for: mountedApp) - } - }.store(in: &mountedApp.subscriptions) + setupSubscription(for: app._phasePublisher, to: \.scenePhase, of: mountedApp) + setupSubscription(for: app._colorSchemePublisher, to: \.colorScheme, of: mountedApp) } } @@ -174,6 +170,22 @@ public final class StackReconciler { }.store(in: &compositeElement.subscriptions) } + private func setupSubscription( + for publisher: AnyPublisher, + to keyPath: WritableKeyPath, + of mountedApp: MountedApp + ) { + publisher.sink { [weak self, weak mountedApp] value in + guard + let mountedApp = mountedApp, + mountedApp.environmentValues[keyPath: keyPath] != value + else { return } + + mountedApp.environmentValues[keyPath: keyPath] = value + self?.queueUpdate(for: mountedApp) + }.store(in: &mountedApp.subscriptions) + } + func render(compositeElement: MountedCompositeElement, body bodyKeypath: ReferenceWritableKeyPath, Any>, result: KeyPath, (Any) -> T>) -> T { diff --git a/Sources/TokamakCore/Tokens/Color.swift b/Sources/TokamakCore/Tokens/Color.swift index 2b58628d8..cfbc88f95 100644 --- a/Sources/TokamakCore/Tokens/Color.swift +++ b/Sources/TokamakCore/Tokens/Color.swift @@ -17,7 +17,7 @@ public struct Color: Hashable, Equatable { // FIXME: This is not injected. - @Environment(\.accentColor) static var envAccentColor: Color? + @Environment(\.accentColor) static var envAccentColor public enum RGBColorSpace { case sRGB diff --git a/Sources/TokamakCore/Tokens/ColorScheme.swift b/Sources/TokamakCore/Tokens/ColorScheme.swift new file mode 100644 index 000000000..cad7615a1 --- /dev/null +++ b/Sources/TokamakCore/Tokens/ColorScheme.swift @@ -0,0 +1,37 @@ +// Copyright 2020 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +public enum ColorScheme: CaseIterable { + case dark + case light +} + +public struct _ColorSchemeKey: EnvironmentKey { + public static var defaultValue: ColorScheme { + fatalError("\(self) must have a renderer-provided default value") + } +} + +public extension EnvironmentValues { + var colorScheme: ColorScheme { + get { self[_ColorSchemeKey.self] } + set { self[_ColorSchemeKey.self] = newValue } + } +} + +public extension View { + func colorScheme(_ colorScheme: ColorScheme) -> some View { + environment(\.colorScheme, colorScheme) + } +} diff --git a/Sources/TokamakCore/Views/Containers/DisclosureGroup.swift b/Sources/TokamakCore/Views/Containers/DisclosureGroup.swift index 85cda3d77..7e5ff44ff 100644 --- a/Sources/TokamakCore/Views/Containers/DisclosureGroup.swift +++ b/Sources/TokamakCore/Views/Containers/DisclosureGroup.swift @@ -20,7 +20,7 @@ public struct DisclosureGroup: View @State var isExpanded: Bool = false let isExpandedBinding: Binding? - @Environment(\._outlineGroupStyle) var style: _OutlineGroupStyle + @Environment(\._outlineGroupStyle) var style let label: Label let content: () -> Content diff --git a/Sources/TokamakCore/Views/Containers/List.swift b/Sources/TokamakCore/Views/Containers/List.swift index 5f56fee91..e89a4d1c3 100644 --- a/Sources/TokamakCore/Views/Containers/List.swift +++ b/Sources/TokamakCore/Views/Containers/List.swift @@ -25,7 +25,7 @@ public struct List: View let selection: _Selection let content: Content - @Environment(\.listStyle) var style: ListStyle + @Environment(\.listStyle) var style public init(selection: Binding>?, @ViewBuilder content: () -> Content) { self.selection = .many(selection) diff --git a/Sources/TokamakCore/Views/NavigationView.swift b/Sources/TokamakCore/Views/NavigationView.swift index 35c7c84cf..9eca0f14c 100644 --- a/Sources/TokamakCore/Views/NavigationView.swift +++ b/Sources/TokamakCore/Views/NavigationView.swift @@ -30,7 +30,7 @@ public struct NavigationView: View where Content: View { } /// This is a helper class that works around absence of "package private" access control in Swift -public struct _NavigationViewProxy { +public struct _NavigationViewProxy: View { public let subject: NavigationView public init(_ subject: NavigationView) { self.subject = subject } diff --git a/Sources/TokamakCore/Views/Selectors/Picker.swift b/Sources/TokamakCore/Views/Selectors/Picker.swift index f270e4317..94faa6b09 100644 --- a/Sources/TokamakCore/Views/Selectors/Picker.swift +++ b/Sources/TokamakCore/Views/Selectors/Picker.swift @@ -16,7 +16,7 @@ public struct _PickerContainer, @@ -36,7 +36,7 @@ public struct _PickerContainer: View where Label: View { let textBinding: Binding let onEditingChanged: (Bool) -> () let onCommit: () -> () - @Environment(\.textFieldStyle) var style: TextFieldStyle + @Environment(\.textFieldStyle) var style public var body: Never { neverBody("TextField") diff --git a/Sources/TokamakCore/Views/Toggle.swift b/Sources/TokamakCore/Views/Toggle.swift index e0fcf5b44..725ba6125 100644 --- a/Sources/TokamakCore/Views/Toggle.swift +++ b/Sources/TokamakCore/Views/Toggle.swift @@ -18,7 +18,7 @@ public struct Toggle