Files
swift-composable-architectu…/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Animations.swift
Brandon Williams 2c93195c23 Prerelease 1.0 (#1929)
* Converted voice memos back to identified array

* update deps

* update docs for DismissEffect

* wip

* Add Sendable conformance to PresentationState (#2086)

* wip

* swift-format

* wip

* wip

* fix some warnings

* docs

* wip

* wip

* Catch some typos in Articles (#2088)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* docs

* wip

* wip

* docs

* wip

* wip

* wip

* wip

* docs

* docs

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Fix invalid states count for 3 optionals and typos (#2094)

* wip

* wip

* more dismisseffect docs

* fixed some references

* navigation doc corrections

* more nav docs

* fix cancellation tests in release mode

* wrap some tests in #if DEBUG since they are testing expected failures

* update UUIDs in tests to use shorter initializer

* fixed a todo

* wip

* fix merge errors

* wip

* fix

* wip

* wip

* fixing a bunch of todos

* get rid of rawvalue in StackElementID

* more todos

* NavLinkStore docs

* fix swift 5.6 stuff

* fix some standups tests

* fix

* clean up

* docs fix

* fixes

* wip

* 5.6 fix

* wip

* wip

* dont parallelize tests

* updated demo readmes

* wip

* Use ObservedObject instead of StateObject for alert/dialog modifiers.

* integration tests for bad dismissal behavior

* check for runtime warnings in every integration test

* wip

* wip

* wip

* fix

* wip

* wip

* wip

* wip

* wip

* wip

* Drop a bunch of Hashables.

* some nav bug fixes

* wip

* wip

* wip

* fix

* fix

* wip

* wip

* Simplify recording test.

* add concurrent async test

* fix

* wip

* Refact how detail dismisses itself.

* fix

* 5.6 fix

* wip

* wip

* wip

* wip

* Add TestStore.assert.

* Revert "Add TestStore.assert."

This reverts commit a892cccc66.

* add Ukrainian Readme.md (#2121)

* Add TestStore.assert. (#2123)

* Add TestStore.assert.

* wip

* Update Sources/ComposableArchitecture/TestStore.swift

Co-authored-by: Stephen Celis <stephen@stephencelis.com>

* Update Sources/ComposableArchitecture/Documentation.docc/Extensions/TestStore.md

Co-authored-by: Stephen Celis <stephen@stephencelis.com>

* fix tests

---------

Co-authored-by: Stephen Celis <stephen@stephencelis.com>

* Run swift-format

* push for store.finish and presentation

* wip

* move docs around

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Add case subscripts

* wip

* wip

* wip

* 5.7-only

* wip

* wip

* wip

* wip

* fix

* revert store.finish task cancellation

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* add test for presentation scope

* wip

* wip

* wip

* wip

* wip

* cleanup

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Rename ReducerProtocol.swift to Reducer.swift (#2206)

* Hard-deprecate old SwitchStore initializers/overloads

* wip

* wip

* Resolve CaseStudies crash (#2258)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Bump timeout for CI

* wip

* wip

---------

Co-authored-by: Jackson Utsch <jutechs@gmail.com>
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
Co-authored-by: 유재호 <y73447jh@gmail.com>
Co-authored-by: Dmytro <barabashdmyto@gmail.com>
Co-authored-by: mbrandonw <mbrandonw@users.noreply.github.com>
2023-07-27 17:35:07 -07:00

172 lines
5.0 KiB
Swift

import ComposableArchitecture
import SwiftUI
private let readMe = """
This screen demonstrates how changes to application state can drive animations. Because the \
`Store` processes actions sent to it synchronously you can typically perform animations \
in the Composable Architecture just as you would in regular SwiftUI.
To animate the changes made to state when an action is sent to the store you can pass along an \
explicit animation, as well, or you can call `viewStore.send` in a `withAnimation` block.
To animate changes made to state through a binding, use the `.animation` method on `Binding`.
To animate asynchronous changes made to state via effects, use `Effect.run` style of effects \
which allows you to send actions with animations.
Try it out by tapping or dragging anywhere on the screen to move the dot, and by flipping the \
toggle at the bottom of the screen.
"""
// MARK: - Feature domain
struct Animations: Reducer {
struct State: Equatable {
var alert: AlertState<Action>?
var circleCenter: CGPoint?
var circleColor = Color.black
var isCircleScaled = false
}
enum Action: Equatable, Sendable {
case alertDismissed
case circleScaleToggleChanged(Bool)
case rainbowButtonTapped
case resetButtonTapped
case resetConfirmationButtonTapped
case setColor(Color)
case tapped(CGPoint)
}
@Dependency(\.continuousClock) var clock
func reduce(into state: inout State, action: Action) -> Effect<Action> {
enum CancelID { case rainbow }
switch action {
case .alertDismissed:
state.alert = nil
return .none
case let .circleScaleToggleChanged(isScaled):
state.isCircleScaled = isScaled
return .none
case .rainbowButtonTapped:
return .run { send in
for color in [Color.red, .blue, .green, .orange, .pink, .purple, .yellow, .black] {
await send(.setColor(color), animation: .linear)
try await self.clock.sleep(for: .seconds(1))
}
}
.cancellable(id: CancelID.rainbow)
case .resetButtonTapped:
state.alert = AlertState {
TextState("Reset state?")
} actions: {
ButtonState(
role: .destructive,
action: .send(.resetConfirmationButtonTapped, animation: .default)
) {
TextState("Reset")
}
ButtonState(role: .cancel) {
TextState("Cancel")
}
}
return .none
case .resetConfirmationButtonTapped:
state = State()
return .cancel(id: CancelID.rainbow)
case let .setColor(color):
state.circleColor = color
return .none
case let .tapped(point):
state.circleCenter = point
return .none
}
}
}
// MARK: - Feature view
struct AnimationsView: View {
let store: StoreOf<Animations>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack(alignment: .leading) {
Text(template: readMe, .body)
.padding()
.gesture(
DragGesture(minimumDistance: 0).onChanged { gesture in
viewStore.send(
.tapped(gesture.location),
animation: .interactiveSpring(response: 0.25, dampingFraction: 0.1)
)
}
)
.overlay {
GeometryReader { proxy in
Circle()
.fill(viewStore.circleColor)
.colorInvert()
.blendMode(.difference)
.frame(width: 50, height: 50)
.scaleEffect(viewStore.isCircleScaled ? 2 : 1)
.position(
x: viewStore.circleCenter?.x ?? proxy.size.width / 2,
y: viewStore.circleCenter?.y ?? proxy.size.height / 2
)
.offset(y: viewStore.circleCenter == nil ? 0 : -44)
}
.allowsHitTesting(false)
}
Toggle(
"Big mode",
isOn:
viewStore
.binding(get: \.isCircleScaled, send: Animations.Action.circleScaleToggleChanged)
.animation(.interactiveSpring(response: 0.25, dampingFraction: 0.1))
)
.padding()
Button("Rainbow") { viewStore.send(.rainbowButtonTapped, animation: .linear) }
.padding([.horizontal, .bottom])
Button("Reset") { viewStore.send(.resetButtonTapped) }
.padding([.horizontal, .bottom])
}
.alert(self.store.scope(state: \.alert, action: { $0 }), dismiss: .alertDismissed)
.navigationBarTitleDisplayMode(.inline)
}
}
}
// MARK: - SwiftUI previews
struct AnimationsView_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
AnimationsView(
store: Store(initialState: Animations.State()) {
Animations()
}
)
}
NavigationView {
AnimationsView(
store: Store(initialState: Animations.State()) {
Animations()
}
)
}
.environment(\.colorScheme, .dark)
}
}
}