Skip to content

Commit 1dd2358

Browse files
committed
Migrate number entry and pad reducers to new features
1 parent b61b4c6 commit 1dd2358

3 files changed

Lines changed: 229 additions & 167 deletions

File tree

App/App/Encouter/EncounterDetailViewState.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,22 @@ extension AddCombatantSheet {
517517
extension EncounterDetailViewState {
518518
static let nullInstance = EncounterDetailViewState(building: Encounter.nullInstance, isMechMuseEnabled: false)
519519
}
520+
521+
struct EncounterDetailFeature: Reducer {
522+
typealias State = EncounterDetailViewState
523+
typealias Action = EncounterDetailViewState.Action
524+
525+
let environment: Environment
526+
527+
init(environment: Environment) {
528+
self.environment = environment
529+
}
530+
531+
var body: some ReducerOf<Self> {
532+
Reduce { state, action in
533+
EncounterDetailViewState.reducer.run(&state, action, environment)
534+
}
535+
}
536+
}
537+
538+
typealias EncounterDetailViewAction = EncounterDetailViewState.Action

App/App/UI/NumberEntryView.swift

Lines changed: 118 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
//
2-
// NumberEntryView.swift
3-
// Construct
4-
//
5-
// Created by Thomas Visser on 02/01/2020.
6-
// Copyright © 2020 Thomas Visser. All rights reserved.
7-
//
8-
91
import Foundation
102
import SwiftUI
113
import ComposableArchitecture
@@ -14,103 +6,154 @@ import Dice
146
import GameModels
157
import Helpers
168

17-
// A view that allows entry of a number, either directly or through a simulated dice roll
18-
struct NumberEntryView: View {
19-
var store: Store<NumberEntryViewState, NumberEntryViewAction>
20-
@ObservedObject var viewStore: ViewStore<NumberEntryViewState, NumberEntryViewAction>
9+
struct NumberEntryFeature: Reducer {
10+
struct State: Equatable {
11+
var mode: Mode
12+
var pad: NumberPadFeature.State
13+
var dice: DiceCalculator.State
2114

22-
init(store: Store<NumberEntryViewState, NumberEntryViewAction>) {
23-
self.store = store
24-
self.viewStore = ViewStore(store, observe: \.self)
25-
}
15+
init(
16+
mode: Mode = .dice,
17+
pad: NumberPadFeature.State = NumberPadFeature.State(value: 0),
18+
dice: DiceCalculator.State = .editingExpression()
19+
) {
20+
self.mode = mode
21+
self.pad = pad
22+
self.dice = dice
23+
}
2624

27-
var body: some View {
28-
VStack {
29-
Picker("Type", selection: viewStore.binding(get: { $0.mode }, send: { .mode($0) }).animation(.spring())) {
30-
Text("Roll").tag(NumberEntryViewState.Mode.dice)
31-
Text("Manual").tag(NumberEntryViewState.Mode.pad)
32-
}.pickerStyle(SegmentedPickerStyle())
33-
34-
if viewStore.state.mode == .dice {
35-
DiceCalculatorView(store: store.scope(state: { $0.diceState }, action: { .dice($0) }))
36-
} else if viewStore.state.mode == .pad {
37-
NumberPadView(store: store.scope(state: { $0.padState }, action: { .pad($0) }))
25+
var value: Int? {
26+
switch mode {
27+
case .pad:
28+
return pad.value
29+
case .dice:
30+
return dice.result(includingIntermediary: false)?.total
3831
}
3932
}
33+
34+
enum Mode: Hashable {
35+
case pad
36+
case dice
37+
}
4038
}
41-
}
4239

43-
struct NumberEntryViewState: Hashable {
44-
var mode: Mode
45-
var padState: NumberPadViewState
46-
var diceState: DiceCalculator.State
40+
enum Action: Equatable {
41+
case mode(State.Mode)
42+
case pad(NumberPadFeature.Action)
43+
case dice(DiceCalculator.Action)
44+
}
4745

48-
var value: Int? {
49-
switch mode {
50-
case .pad: return padState.value
51-
case .dice: return diceState.result(includingIntermediary: false)?.total
52-
}
46+
typealias Environment = NumberEntryViewEnvironment
47+
48+
let environment: Environment
49+
50+
init(environment: Environment) {
51+
self.environment = environment
5352
}
5453

55-
enum Mode: Hashable {
56-
case pad
57-
case dice
54+
var body: some ReducerOf<Self> {
55+
Scope(state: \State.pad, action: /Action.pad) {
56+
NumberPadFeature()
57+
}
58+
59+
Scope(state: \State.dice, action: /Action.dice) {
60+
DiceCalculator(environment: environment)
61+
}
62+
63+
Reduce { state, action in
64+
switch action {
65+
case .mode(let mode):
66+
state.mode = mode
67+
case .pad, .dice:
68+
break
69+
}
70+
return .none
71+
}
5872
}
5973
}
6074

61-
enum NumberEntryViewAction: Equatable {
62-
case mode(NumberEntryViewState.Mode)
63-
case pad(NumberPadViewAction)
64-
case dice(DiceCalculator.Action)
65-
}
75+
typealias NumberEntryViewEnvironment = EnvironmentWithModifierFormatter & EnvironmentWithMainQueue & EnvironmentWithDiceLog
6676

67-
extension NumberEntryViewState {
68-
static func pad(value: Int, expression: DiceExpression? = nil) -> NumberEntryViewState {
69-
return NumberEntryViewState(mode: .pad, padState: NumberPadViewState(value: value), diceState: expression.map { .rollingExpression($0) } ?? .editingExpression())
77+
extension NumberEntryFeature.State {
78+
static func pad(value: Int, expression: DiceExpression? = nil) -> Self {
79+
Self(
80+
mode: .pad,
81+
pad: NumberPadFeature.State(value: value),
82+
dice: expression.map { .rollingExpression($0) } ?? .editingExpression()
83+
)
7084
}
7185

72-
static func dice(_ state: DiceCalculator.State) -> NumberEntryViewState {
73-
return NumberEntryViewState(mode: .dice, padState: NumberPadViewState(value: 0), diceState: state)
86+
static func dice(_ state: DiceCalculator.State) -> Self {
87+
Self(
88+
mode: .dice,
89+
pad: NumberPadFeature.State(value: 0),
90+
dice: state
91+
)
7492
}
7593

76-
static func initiative(combatant: Combatant) -> NumberEntryViewState {
94+
static func initiative(combatant: Combatant) -> Self {
7795
if combatant.definition.player != nil {
78-
return NumberEntryViewState.pad(
96+
return .pad(
7997
value: combatant.initiative ?? 0,
8098
expression: combatant.definition.initiativeModifier.map { 1.d(20) + $0 }
8199
)
82100
} else if let mod = combatant.definition.initiativeModifier {
83-
return NumberEntryViewState.dice(.rollingExpression(1.d(20) + mod, prefilledResult: combatant.initiative))
101+
return .dice(.rollingExpression(1.d(20) + mod, prefilledResult: combatant.initiative))
84102
} else if let initiative = combatant.initiative {
85-
return NumberEntryViewState.dice(.rollingExpression(1.d(20), prefilledResult: initiative))
103+
return .dice(.rollingExpression(1.d(20), prefilledResult: initiative))
86104
} else {
87-
return NumberEntryViewState.dice(.editingExpression(1.d(20)))
105+
return .dice(.editingExpression(1.d(20)))
88106
}
89107
}
90108

91-
static var reducer: AnyReducer<Self, NumberEntryViewAction, NumberEntryViewEnvironment> = AnyReducer.combine(
92-
AnyReducer { state, action, _ in
93-
switch action {
94-
case .mode(let m):
95-
state.mode = m
96-
case .pad, .dice: break // handled by reducers below
109+
static let nullInstance = Self(
110+
mode: .dice,
111+
pad: NumberPadFeature.State(value: 0),
112+
dice: DiceCalculator.State(displayOutcomeExternally: false, rollOnAppear: false, expression: .number(0), mode: .editingExpression)
113+
)
114+
}
115+
116+
struct NumberEntryView: View {
117+
let store: StoreOf<NumberEntryFeature>
118+
119+
init(store: StoreOf<NumberEntryFeature>) {
120+
self.store = store
121+
}
122+
123+
var body: some View {
124+
WithViewStore(store, observe: \.self) { viewStore in
125+
VStack {
126+
Picker(
127+
"Type",
128+
selection: viewStore.binding(
129+
get: \.mode,
130+
send: NumberEntryFeature.Action.mode
131+
).animation(.spring())
132+
) {
133+
Text("Roll").tag(NumberEntryFeature.State.Mode.dice)
134+
Text("Manual").tag(NumberEntryFeature.State.Mode.pad)
135+
}
136+
.pickerStyle(.segmented)
137+
138+
if viewStore.state.mode == .dice {
139+
DiceCalculatorView(store: store.scope(state: \.dice, action: NumberEntryFeature.Action.dice))
140+
} else {
141+
NumberPadView(store: store.scope(state: \.pad, action: NumberEntryFeature.Action.pad))
142+
}
97143
}
98-
return .none
99-
},
100-
NumberPadViewState.reducer.pullback(state: \.padState, action: /NumberEntryViewAction.pad, environment: { _ in () }),
101-
AnyReducer { env in
102-
DiceCalculator(environment: env)
103144
}
104-
.pullback(
105-
state: \.diceState,
106-
action: /NumberEntryViewAction.dice,
107-
environment: { $0 }
108-
)
109-
)
145+
}
110146
}
111147

112-
typealias NumberEntryViewEnvironment = EnvironmentWithModifierFormatter & EnvironmentWithMainQueue & EnvironmentWithDiceLog
113148

114-
extension NumberEntryViewState {
115-
static let nullInstance = NumberEntryViewState(mode: .dice, padState: NumberPadViewState(value: 0), diceState: DiceCalculator.State(displayOutcomeExternally: false, rollOnAppear: false, expression: .number(0), mode: .editingExpression))
149+
typealias NumberEntryViewState = NumberEntryFeature.State
150+
typealias NumberEntryViewAction = NumberEntryFeature.Action
151+
152+
extension NumberEntryFeature.State {
153+
static var reducer: AnyReducer<Self, NumberEntryFeature.Action, NumberEntryViewEnvironment> {
154+
AnyReducer { state, action, environment in
155+
let feature = NumberEntryFeature(environment: environment)
156+
return feature.reduce(into: &state, action: action)
157+
}
158+
}
116159
}

0 commit comments

Comments
 (0)