Implement logic for saving new DiceRolls to Core Data
This commit is contained in:
parent
4ce8accdc5
commit
fd9cb5c429
|
@ -11,28 +11,55 @@ import Foundation
|
|||
import Combine
|
||||
import CypherPoetSwiftUIKit_DataFlowUtils
|
||||
import CypherPoetPropertyWrappers_UserDefault
|
||||
import CypherPoetCoreDataKit
|
||||
|
||||
|
||||
struct DiceGeneratorState {
|
||||
|
||||
@UserDefault("Dice Generator State :: roll size", defaultValue: 1)
|
||||
var rollSize: Int
|
||||
|
||||
|
||||
@UserDefault("Dice Generator State :: allows repeat results", defaultValue: true)
|
||||
var allowsRepeatResults: Bool
|
||||
|
||||
var latestRoll: DiceRoll?
|
||||
}
|
||||
|
||||
|
||||
|
||||
//enum DiceGeneratorSideEffect: SideEffect {
|
||||
//
|
||||
//}
|
||||
enum DiceGeneratorSideEffect: SideEffect {
|
||||
case recordNewRoll([Dice])
|
||||
|
||||
func mapToAction() -> AnyPublisher<AppAction, Never> {
|
||||
switch self {
|
||||
case .recordNewRoll(let rollResults):
|
||||
return Just(rollResults)
|
||||
.map { diceCollection -> DiceRoll in
|
||||
let context = CurrentApp.coreDataManager.backgroundContext
|
||||
let diceRoll = DiceRoll(context: context)
|
||||
|
||||
diceRoll.createdAt = Date()
|
||||
diceRoll.diceValues = diceCollection.map { $0.rawValue }
|
||||
|
||||
return diceRoll
|
||||
}
|
||||
.flatMap { diceRoll in
|
||||
CurrentApp.coreDataManager.save(diceRoll.managedObjectContext!)
|
||||
.map { AppAction.diceGenerator(.latestRollSet(diceRoll)) }
|
||||
.catch { error in
|
||||
// 📝 Better error handling could be placed here.
|
||||
Just(AppAction.diceGenerator(.rollSizeSet(diceRoll.diceValues.count)))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum DiceGeneratorAction {
|
||||
case rollSizeSet(Int)
|
||||
case latestRollSet(DiceRoll)
|
||||
}
|
||||
|
||||
|
||||
|
@ -42,6 +69,9 @@ let diceGeneratorReducer: Reducer<DiceGeneratorState, DiceGeneratorAction> = Red
|
|||
switch action {
|
||||
case .rollSizeSet(let size):
|
||||
state.rollSize = size
|
||||
case .latestRollSet(let diceRoll):
|
||||
state.latestRoll = diceRoll
|
||||
state.rollSize = diceRoll.diceValues.count
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -10,16 +10,8 @@ import SwiftUI
|
|||
|
||||
|
||||
struct DiceGeneratorContainerView {
|
||||
@Environment(\.managedObjectContext) var managedObjectContext
|
||||
@EnvironmentObject private var store: AppStore
|
||||
|
||||
private var diceCount: Binding<Int> {
|
||||
store.binding(
|
||||
for: \.diceGeneratorState.rollSize,
|
||||
onChange: { .diceGenerator(.rollSizeSet($0)) }
|
||||
)
|
||||
}
|
||||
|
||||
@State private var isShowingRollHistory = false
|
||||
}
|
||||
|
||||
|
@ -30,7 +22,8 @@ extension DiceGeneratorContainerView: View {
|
|||
var body: some View {
|
||||
DiceGeneratorView(
|
||||
viewModel: .init(
|
||||
diceCount: diceCount
|
||||
diceCount: store.state.diceGeneratorState.rollSize,
|
||||
diceCollection: store.state.diceGeneratorState.latestRoll?.diceArray
|
||||
),
|
||||
onDiceRolled: onDiceRolled(_:)
|
||||
)
|
||||
|
@ -55,10 +48,8 @@ extension DiceGeneratorContainerView {
|
|||
// MARK: - Private Helpers
|
||||
private extension DiceGeneratorContainerView {
|
||||
|
||||
func onDiceRolled(_ diceRoll: DiceRoll) {
|
||||
guard let context = diceRoll.managedObjectContext else { preconditionFailure() }
|
||||
|
||||
CurrentApp.coreDataManager.save(context)
|
||||
func onDiceRolled(_ rollResults: [Dice]) {
|
||||
store.send(DiceGeneratorSideEffect.recordNewRoll(rollResults))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,25 +11,23 @@ import SwiftUI
|
|||
import Combine
|
||||
|
||||
|
||||
|
||||
extension DiceGeneratorView {
|
||||
final class ViewModel: ObservableObject {
|
||||
private var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
private var rollResults: [Dice] = []
|
||||
|
||||
@Binding var diceCount: Int
|
||||
|
||||
|
||||
// MARK: - Published Outputs
|
||||
@Published var diceCollection: [Dice] = []
|
||||
@Published var diceCount: Int
|
||||
@Published var rollResults: [Dice] = []
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
init(
|
||||
diceCount: Binding<Int>
|
||||
diceCount: Int,
|
||||
diceCollection: [Dice]? = nil
|
||||
) {
|
||||
self._diceCount = diceCount
|
||||
self.diceCount = diceCount
|
||||
self.rollResults = diceCollection ?? (1...diceCount).map { _ in .one }
|
||||
|
||||
setupSubscribers()
|
||||
}
|
||||
|
@ -41,16 +39,10 @@ extension DiceGeneratorView {
|
|||
// MARK: - Publishers
|
||||
extension DiceGeneratorView.ViewModel {
|
||||
|
||||
private var rollResultsPublisher: AnyPublisher<[Dice], Never> {
|
||||
CurrentValueSubject(rollResults)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
private var diceCollectionPublisher: Publishers.Share<AnyPublisher<[Dice], Never>> {
|
||||
Publishers.CombineLatest(
|
||||
CurrentValueSubject(diceCount),
|
||||
rollResultsPublisher
|
||||
$diceCount,
|
||||
CurrentValueSubject(rollResults)
|
||||
)
|
||||
.print("diceCollectionPublisher")
|
||||
.map { (diceCount, results) in
|
||||
|
@ -74,12 +66,15 @@ extension DiceGeneratorView.ViewModel {
|
|||
|
||||
// MARK: - Computeds
|
||||
extension DiceGeneratorView.ViewModel {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public Methods
|
||||
extension DiceGeneratorView.ViewModel {
|
||||
|
||||
func generateRollResults() -> [Dice] {
|
||||
(1...diceCount).map { _ in Dice(rawValue: Int16.random(in: 1...6))! }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -90,7 +85,7 @@ private extension DiceGeneratorView.ViewModel {
|
|||
func setupSubscribers() {
|
||||
diceCollectionPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.diceCollection, on: self)
|
||||
.assign(to: \.rollResults, on: self)
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,15 @@ import SwiftUI
|
|||
|
||||
|
||||
struct DiceGeneratorView {
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) var verticalSizeClass
|
||||
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
private let diceCountRange = 1...6
|
||||
private let diceRollAnimationDuration = 0.76
|
||||
|
||||
|
||||
@ObservedObject var viewModel: ViewModel
|
||||
let onDiceRolled: ((DiceRoll) -> Void)?
|
||||
let onDiceRolled: (([Dice]) -> Void)?
|
||||
|
||||
|
||||
@State private var isShakingDice = false
|
||||
@State private var isShowingDice = true
|
||||
|
@ -56,18 +57,6 @@ extension DiceGeneratorView {
|
|||
}
|
||||
|
||||
|
||||
var diceCollectionTransition: AnyTransition {
|
||||
.asymmetric(
|
||||
insertion: AnyTransition
|
||||
.move(edge: .bottom)
|
||||
.combined(with: .scale(scale: 0, anchor: .top)),
|
||||
|
||||
removal: AnyTransition
|
||||
.opacity
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
var diceRollAnimation: Animation {
|
||||
Animation.spring(
|
||||
response: diceRollAnimationDuration,
|
||||
|
@ -85,7 +74,6 @@ extension DiceGeneratorView {
|
|||
}
|
||||
|
||||
|
||||
// TODO: Improve this logic
|
||||
func offsetFromShake(in geometry: GeometryProxy) -> CGSize {
|
||||
.init(
|
||||
width: isShakingDice ? geometry.size.width / 2 : 0,
|
||||
|
@ -103,9 +91,9 @@ extension DiceGeneratorView {
|
|||
GeometryReader { geometry in
|
||||
Group {
|
||||
if self.isShowingHorizontalDiceLayout {
|
||||
HorizontalDiceRollView(diceCollection: self.viewModel.diceCollection)
|
||||
HorizontalDiceRollView(diceCollection: self.viewModel.rollResults)
|
||||
} else {
|
||||
VerticalDiceRollView(diceCollection: self.viewModel.diceCollection) { (index, dice, position, sideLength) in
|
||||
VerticalDiceRollView(diceCollection: self.viewModel.rollResults) { (index, dice, position, sideLength) in
|
||||
DiceView(dice: dice)
|
||||
.frame(
|
||||
width: sideLength,
|
||||
|
@ -129,13 +117,10 @@ extension DiceGeneratorView {
|
|||
.offset(self.offsetFromShake(in: geometry))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private var rollButton: some View {
|
||||
Button(action: {
|
||||
// self.onDiceRolled?(viewModel.diceRollFromForm)
|
||||
|
||||
// Shake the current set
|
||||
withAnimation(
|
||||
Animation
|
||||
|
@ -150,12 +135,11 @@ extension DiceGeneratorView {
|
|||
self.isShakingDice = false
|
||||
self.isShowingDice = false
|
||||
self.diceRollCompletion = 0.0
|
||||
self.onDiceRolled?(self.viewModel.generateRollResults())
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.isShowingDice = true
|
||||
|
||||
// 📝 TODO: Compute and set the new dice values here
|
||||
|
||||
withAnimation(self.diceRollAnimation) {
|
||||
self.diceRollCompletion = 1.0
|
||||
}
|
||||
|
@ -184,7 +168,8 @@ extension DiceGeneratorView {
|
|||
Spacer()
|
||||
|
||||
Text("\(viewModel.diceCount)")
|
||||
Stepper("Dice Count", value: viewModel.$diceCount, in: diceCountRange)
|
||||
|
||||
Stepper("Dice Count", value: $viewModel.diceCount, in: diceCountRange)
|
||||
.labelsHidden()
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +192,8 @@ struct DiceGeneratorView_Previews: PreviewProvider {
|
|||
static var previews: some View {
|
||||
DiceGeneratorView(
|
||||
viewModel: .init(
|
||||
diceCount: .constant(2)
|
||||
// diceCount: .constant(2)
|
||||
diceCount: 2
|
||||
),
|
||||
onDiceRolled: { _ in }
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue