Implement logic for saving new DiceRolls to Core Data

This commit is contained in:
CypherPoet 2020-02-13 09:39:40 -06:00
parent 4ce8accdc5
commit fd9cb5c429
4 changed files with 64 additions and 62 deletions

View File

@ -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
}
}
)

View File

@ -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))
}
}

View File

@ -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)
}
}

View File

@ -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 }
)