Files
swift-composable-architectu…/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Animations.swift
Stephen Celis 57e804f1cc Macro bonanza (#2553)
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Silence test warnings

* wip

* wip

* wip

* update a bunch of docs

* wip

* wip

* fix

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Kill integration tests for now

* wip

* wip

* wip

* wip

* updating docs for @Reducer macro

* replaced more Reducer protocols with @Reducer

* Fixed some broken docc references

* wip

* Some @Reducer docs

* more docs

* convert some old styles to new style

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* bump

* update tutorials to use body

* update tutorials to use DML on destination state enum

* Add diagnostic

* wip

* updated a few more tests

* wip

* wip

* Add another gotcha

* wip

* wip

* wip

* fixes

* wip

* wip

* wip

* wip

* wip

* fix

* wip

* remove for now

* wip

* wip

* updated some docs

* migration guides

* more migration guide

* fix ci

* fix

* soft deprecate all apis using AnyCasePath

* wip

* Fix

* fix tests

* swift-format 509 compatibility

* wip

* wip

* Update Sources/ComposableArchitecture/Macros.swift

Co-authored-by: Mateusz Bąk <bakmatthew@icloud.com>

* wip

* wip

* update optional state case study

* remove initializer

* Don't use @State for BasicsView integration demo

* fix tests

* remove reduce diagnostics for now

* diagnose error not warning

* Update Sources/ComposableArchitecture/Macros.swift

Co-authored-by: Jesse Tipton <jesse@jessetipton.com>

* wip

* move integration tests to cron

* Revert "move integration tests to cron"

This reverts commit f9bdf2f04b.

* disable flakey tests on CI

* wip

* wip

* Revert "Revert "move integration tests to cron""

This reverts commit 66aafa7327.

* fix

* wip

* fix

---------

Co-authored-by: Brandon Williams <mbrandonw@hey.com>
Co-authored-by: Mateusz Bąk <bakmatthew@icloud.com>
Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com>
Co-authored-by: Jesse Tipton <jesse@jessetipton.com>
2023-11-13 12:57:35 -08:00

180 lines
5.2 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
@Reducer
struct Animations {
struct State: Equatable {
@PresentationState var alert: AlertState<Action.Alert>?
var circleCenter: CGPoint?
var circleColor = Color.black
var isCircleScaled = false
}
enum Action: Sendable {
case alert(PresentationAction<Alert>)
case circleScaleToggleChanged(Bool)
case rainbowButtonTapped
case resetButtonTapped
case setColor(Color)
case tapped(CGPoint)
enum Alert: Sendable {
case resetConfirmationButtonTapped
}
}
@Dependency(\.continuousClock) var clock
private enum CancelID { case rainbow }
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .alert(.presented(.resetConfirmationButtonTapped)):
state = State()
return .cancel(id: CancelID.rainbow)
case .alert:
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 let .setColor(color):
state.circleColor = color
return .none
case let .tapped(point):
state.circleCenter = point
return .none
}
}
.ifLet(\.$alert, action: \.alert)
}
}
// MARK: - Feature view
struct AnimationsView: View {
@State var store = Store(initialState: Animations.State()) {
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: { .circleScaleToggleChanged($0) })
.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(store: self.store.scope(state: \.$alert, action: { .alert($0) }))
.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)
}
}
}