From b9db9885059f2cf402909ff67837f51602a2adf9 Mon Sep 17 00:00:00 2001 From: Said Tahsin Dane Date: Wed, 10 Jan 2018 14:37:08 +0100 Subject: [PATCH 1/8] GoL: Introduce MVP pattern for app And move controlButtonLabel logic to model and presenter --- .../com/novoda/gol/presentation/AppModel.kt | 18 +++++++++++ .../novoda/gol/presentation/AppPresenter.kt | 21 +++++++++++++ .../com/novoda/gol/presentation/AppView.kt | 9 ++++++ .../kotlin/com/novoda/gol/components/App.kt | 30 +++++++++++++++---- 4 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppModel.kt create mode 100644 game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt create mode 100644 game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppModel.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppModel.kt new file mode 100644 index 000000000..37c6ca597 --- /dev/null +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppModel.kt @@ -0,0 +1,18 @@ +package com.novoda.gol.presentation + +import kotlin.properties.Delegates.observable + +class AppModel { + + private var isIdle by observable(true) { _, _, newValue -> + onSimulationStateChanged(newValue) + } + + var onSimulationStateChanged: (isIdle: Boolean) -> Unit by observable<(Boolean) -> Unit>({}) { _, _, newValue -> + newValue(isIdle) + } + + fun toggleSimulation() { + isIdle = isIdle.not() + } +} diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt new file mode 100644 index 000000000..7a1573359 --- /dev/null +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt @@ -0,0 +1,21 @@ +package com.novoda.gol.presentation + +class AppPresenter { + + private val model = AppModel() + + fun bind(view: AppView) { + model.onSimulationStateChanged = { isIdle -> + view.controlButtonLabel = if (isIdle) "Start simulation" else "Stop Simulation" + } + + view.onControlButtonClicked = { + model.toggleSimulation() + } + } + + fun unbind(view: AppView) { + model.onSimulationStateChanged = {} + view.onControlButtonClicked = {} + } +} diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt new file mode 100644 index 000000000..77f9ab647 --- /dev/null +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt @@ -0,0 +1,9 @@ +package com.novoda.gol.presentation + +interface AppView { + + var controlButtonLabel: String + + var onControlButtonClicked : () -> Unit + +} diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt index 82d924f72..929e0e204 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt @@ -4,12 +4,33 @@ package com.novoda.gol.components import com.novoda.gol.patterns.PatternEntity import com.novoda.gol.patterns.PatternRepository +import com.novoda.gol.presentation.AppPresenter +import com.novoda.gol.presentation.AppView import kotlinx.html.style import react.* import react.dom.div import react.dom.h2 +import kotlin.properties.Delegates.observable -class App : RComponent() { +class App : RComponent(), AppView { + + override var onControlButtonClicked: () -> Unit = {} + + override var controlButtonLabel by observable("") { _, _, newValue -> + setState { + controlButtonLabel = newValue + } + } + + private val presenter: AppPresenter = AppPresenter() + + override fun componentWillMount() { + presenter.bind(this) + } + + override fun componentWillUnmount() { + presenter.unbind(this) + } override fun State.init() { patternEntities = PatternRepository.patterns() @@ -23,7 +44,8 @@ class App : RComponent() { flexDirection = "column" } - controlButton(controlButtonLabel(), { + controlButton(state.controlButtonLabel, { + onControlButtonClicked() setState { isIdle = isIdle.not() } @@ -57,15 +79,13 @@ class App : RComponent() { } } } - - private fun controlButtonLabel() = if (state.isIdle) "Start simulation" else "Stop Simulation" - } interface State : RState { var patternEntities: List var isIdle: Boolean var selectedPattern: PatternEntity? + var controlButtonLabel: String } fun RBuilder.app() = child(App::class) {} From f4b2aec58923444e26a6fb99199ef17ddc11b15f Mon Sep 17 00:00:00 2001 From: Said Tahsin Dane Date: Wed, 10 Jan 2018 15:02:03 +0100 Subject: [PATCH 2/8] GoL: Move pattern visibility logic to Presenter --- .../com/novoda/gol/presentation/AppPresenter.kt | 2 ++ .../kotlin/com/novoda/gol/presentation/AppView.kt | 1 + .../com/novoda/gol/presentation/PatternViewState.kt | 8 ++++++++ .../src/main/kotlin/com/novoda/gol/components/App.kt | 12 +++++++++++- .../main/kotlin/com/novoda/gol/components/Board.kt | 4 +++- 5 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/PatternViewState.kt diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt index 7a1573359..a9b30082a 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt @@ -5,8 +5,10 @@ class AppPresenter { private val model = AppModel() fun bind(view: AppView) { + model.onSimulationStateChanged = { isIdle -> view.controlButtonLabel = if (isIdle) "Start simulation" else "Stop Simulation" + view.patternSelectionVisibility = isIdle } view.onControlButtonClicked = { diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt index 77f9ab647..27bafcc77 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt @@ -3,6 +3,7 @@ package com.novoda.gol.presentation interface AppView { var controlButtonLabel: String + var patternSelectionVisibility: Boolean var onControlButtonClicked : () -> Unit diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/PatternViewState.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/PatternViewState.kt new file mode 100644 index 000000000..4ea6cf003 --- /dev/null +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/PatternViewState.kt @@ -0,0 +1,8 @@ +package com.novoda.gol.presentation + +import com.novoda.gol.patterns.PatternEntity + +data class PatternViewState( + var shouldDisplay: Boolean, + val patternEntities: List +) diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt index 929e0e204..d1206af59 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt @@ -6,6 +6,7 @@ import com.novoda.gol.patterns.PatternEntity import com.novoda.gol.patterns.PatternRepository import com.novoda.gol.presentation.AppPresenter import com.novoda.gol.presentation.AppView +import com.novoda.gol.presentation.PatternViewState import kotlinx.html.style import react.* import react.dom.div @@ -22,6 +23,12 @@ class App : RComponent(), AppView { } } + override var patternSelectionVisibility by observable(true) { _, _, newValue -> + setState { + patternViewState.shouldDisplay = newValue + } + } + private val presenter: AppPresenter = AppPresenter() override fun componentWillMount() { @@ -33,6 +40,7 @@ class App : RComponent(), AppView { } override fun State.init() { + patternViewState = PatternViewState(true, PatternRepository.patterns()) patternEntities = PatternRepository.patterns() isIdle = true } @@ -58,7 +66,8 @@ class App : RComponent(), AppView { board(state.isIdle, state.selectedPattern) - if (state.isIdle) { + console.log("shouldDisplay: ${state.patternViewState.shouldDisplay}") + if (state.patternViewState.shouldDisplay) { div { @@ -82,6 +91,7 @@ class App : RComponent(), AppView { } interface State : RState { + var patternViewState: PatternViewState var patternEntities: List var isIdle: Boolean var selectedPattern: PatternEntity? diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt index 1f3275c76..5727d11db 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt @@ -50,7 +50,9 @@ class Board(boardProps: BoardProps) : RComponent(boardPr } else { onStopSimulationClicked() } - onPatternSelected.invoke(nextProps.selectedPattern!!) + if (props.selectedPattern != null) { + onPatternSelected.invoke(props.selectedPattern!!) + } } override fun RBuilder.render() = renderBoard(state) From 0a861353428925915ce9667552699b70aadbfd8f Mon Sep 17 00:00:00 2001 From: Said Tahsin Dane Date: Wed, 10 Jan 2018 15:09:02 +0100 Subject: [PATCH 3/8] GoL: Introduce Logger --- .../common/src/main/kotlin/com/novoda/gol/Logger.kt | 6 ++++++ .../src/main/kotlin/com/novoda/gol/Logger.kt | 8 ++++++++ .../src/main/kotlin/com/novoda/gol/Logger.kt | 9 +++++++++ 3 files changed, 23 insertions(+) create mode 100644 game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/Logger.kt create mode 100644 game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/Logger.kt create mode 100644 game-of-life-multiplatform/game-of-life-jvm/src/main/kotlin/com/novoda/gol/Logger.kt diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/Logger.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/Logger.kt new file mode 100644 index 000000000..434a1b419 --- /dev/null +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/Logger.kt @@ -0,0 +1,6 @@ +package com.novoda.gol + +expect object Logger { + + fun log(o: Any?) +} diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/Logger.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/Logger.kt new file mode 100644 index 000000000..efb463bf3 --- /dev/null +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/Logger.kt @@ -0,0 +1,8 @@ +package com.novoda.gol + +actual object Logger { + + actual fun log(o: Any?) { + console.log(o) + } +} diff --git a/game-of-life-multiplatform/game-of-life-jvm/src/main/kotlin/com/novoda/gol/Logger.kt b/game-of-life-multiplatform/game-of-life-jvm/src/main/kotlin/com/novoda/gol/Logger.kt new file mode 100644 index 000000000..18620a6ea --- /dev/null +++ b/game-of-life-multiplatform/game-of-life-jvm/src/main/kotlin/com/novoda/gol/Logger.kt @@ -0,0 +1,9 @@ +package com.novoda.gol + +actual object Logger { + + actual fun log(o: Any?) { + System.out.println(o) + } + +} From d4a43aa90f7bf1ed6115cbbd79be9ffb6e82b3b7 Mon Sep 17 00:00:00 2001 From: Said Tahsin Dane Date: Wed, 10 Jan 2018 15:14:24 +0100 Subject: [PATCH 4/8] GoL: Use the correct props --- .../src/main/kotlin/com/novoda/gol/components/Board.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt index 5727d11db..72992dfee 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt @@ -50,8 +50,8 @@ class Board(boardProps: BoardProps) : RComponent(boardPr } else { onStopSimulationClicked() } - if (props.selectedPattern != null) { - onPatternSelected.invoke(props.selectedPattern!!) + if (nextProps.selectedPattern != null) { + onPatternSelected.invoke(nextProps.selectedPattern!!) } } From af1aa2098347cb34265b125c229cdb75322f9a64 Mon Sep 17 00:00:00 2001 From: Said Tahsin Dane Date: Wed, 10 Jan 2018 15:16:02 +0100 Subject: [PATCH 5/8] GoL: Use the new pattern list in viewState and remove the old one --- .../src/main/kotlin/com/novoda/gol/components/App.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt index d1206af59..1baedc579 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt @@ -41,7 +41,6 @@ class App : RComponent(), AppView { override fun State.init() { patternViewState = PatternViewState(true, PatternRepository.patterns()) - patternEntities = PatternRepository.patterns() isIdle = true } @@ -66,7 +65,6 @@ class App : RComponent(), AppView { board(state.isIdle, state.selectedPattern) - console.log("shouldDisplay: ${state.patternViewState.shouldDisplay}") if (state.patternViewState.shouldDisplay) { div { @@ -77,7 +75,7 @@ class App : RComponent(), AppView { h2 { +"Choose a pattern" } - for (patternEntity in state.patternEntities) { + for (patternEntity in state.patternViewState.patternEntities) { pattern(patternEntity, { setState { selectedPattern = patternEntity @@ -92,7 +90,6 @@ class App : RComponent(), AppView { interface State : RState { var patternViewState: PatternViewState - var patternEntities: List var isIdle: Boolean var selectedPattern: PatternEntity? var controlButtonLabel: String From 72db5a5cf3d4be518f55ad50bc726ee00ca5c25f Mon Sep 17 00:00:00 2001 From: Said Tahsin Dane Date: Wed, 10 Jan 2018 15:29:38 +0100 Subject: [PATCH 6/8] GoL: Move the rest of the logic from view to presenter. isIdle and patternSelection are moved --- .../novoda/gol/presentation/AppPresenter.kt | 5 ++++ .../com/novoda/gol/presentation/AppView.kt | 4 +++ .../novoda/gol/presentation/BoardViewState.kt | 8 ++++++ .../kotlin/com/novoda/gol/components/App.kt | 25 ++++++++++--------- .../kotlin/com/novoda/gol/components/Board.kt | 3 +++ 5 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardViewState.kt diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt index a9b30082a..1fbc99549 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt @@ -9,11 +9,16 @@ class AppPresenter { model.onSimulationStateChanged = { isIdle -> view.controlButtonLabel = if (isIdle) "Start simulation" else "Stop Simulation" view.patternSelectionVisibility = isIdle + view.board = view.board.copy(isIdle = isIdle) } view.onControlButtonClicked = { model.toggleSimulation() } + + view.onPatternSelected = { pattern -> + view.board = view.board.copy(selectedPattern = pattern) + } } fun unbind(view: AppView) { diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt index 27bafcc77..0f903d660 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt @@ -1,10 +1,14 @@ package com.novoda.gol.presentation +import com.novoda.gol.patterns.PatternEntity + interface AppView { var controlButtonLabel: String var patternSelectionVisibility: Boolean + var board: BoardViewState var onControlButtonClicked : () -> Unit + var onPatternSelected: (pattern : PatternEntity) -> Unit } diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardViewState.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardViewState.kt new file mode 100644 index 000000000..b11527e7e --- /dev/null +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardViewState.kt @@ -0,0 +1,8 @@ +package com.novoda.gol.presentation + +import com.novoda.gol.patterns.PatternEntity + +data class BoardViewState( + val isIdle: Boolean, + val selectedPattern: PatternEntity? +) diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt index 1baedc579..d3f29261d 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt @@ -6,6 +6,7 @@ import com.novoda.gol.patterns.PatternEntity import com.novoda.gol.patterns.PatternRepository import com.novoda.gol.presentation.AppPresenter import com.novoda.gol.presentation.AppView +import com.novoda.gol.presentation.BoardViewState import com.novoda.gol.presentation.PatternViewState import kotlinx.html.style import react.* @@ -16,6 +17,7 @@ import kotlin.properties.Delegates.observable class App : RComponent(), AppView { override var onControlButtonClicked: () -> Unit = {} + override var onPatternSelected: (pattern: PatternEntity) -> Unit = {} override var controlButtonLabel by observable("") { _, _, newValue -> setState { @@ -29,6 +31,12 @@ class App : RComponent(), AppView { } } + override var board by observable(BoardViewState(true, null)) { _, _, newValue -> + setState { + boardViewState = newValue + } + } + private val presenter: AppPresenter = AppPresenter() override fun componentWillMount() { @@ -41,7 +49,6 @@ class App : RComponent(), AppView { override fun State.init() { patternViewState = PatternViewState(true, PatternRepository.patterns()) - isIdle = true } override fun RBuilder.render(): ReactElement? = @@ -53,9 +60,6 @@ class App : RComponent(), AppView { controlButton(state.controlButtonLabel, { onControlButtonClicked() - setState { - isIdle = isIdle.not() - } }) div { @@ -63,7 +67,7 @@ class App : RComponent(), AppView { display = "flex" } - board(state.isIdle, state.selectedPattern) + board(state.boardViewState) if (state.patternViewState.shouldDisplay) { @@ -75,11 +79,9 @@ class App : RComponent(), AppView { h2 { +"Choose a pattern" } - for (patternEntity in state.patternViewState.patternEntities) { - pattern(patternEntity, { - setState { - selectedPattern = patternEntity - } + state.patternViewState.patternEntities.forEach { pattern -> + pattern(pattern, { + onPatternSelected(pattern) }) } } @@ -90,8 +92,7 @@ class App : RComponent(), AppView { interface State : RState { var patternViewState: PatternViewState - var isIdle: Boolean - var selectedPattern: PatternEntity? + var boardViewState: BoardViewState var controlButtonLabel: String } diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt index 72992dfee..40eb4b72f 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt @@ -7,6 +7,7 @@ import com.novoda.gol.data.PositionEntity import com.novoda.gol.patterns.PatternEntity import com.novoda.gol.presentation.BoardPresenter import com.novoda.gol.presentation.BoardView +import com.novoda.gol.presentation.BoardViewState import kotlinext.js.js import kotlinx.html.style import react.* @@ -82,6 +83,8 @@ interface BoardState : RState { var boardEntity: BoardEntity } +fun RBuilder.board(board: BoardViewState) = board(board.isIdle, board.selectedPattern) + fun RBuilder.board(isIdle: Boolean, selectedPattern: PatternEntity? = null) = child( Board::class) { attrs.isIdle = isIdle From 82ee550932ab4706bb0da35e1e61cbe2ddeb2708 Mon Sep 17 00:00:00 2001 From: Said Tahsin Dane Date: Wed, 10 Jan 2018 15:58:42 +0100 Subject: [PATCH 7/8] GoL: Replace observable properties in View with methods. With this, the default value and the property (as state) is also moved to Model --- .../com/novoda/gol/presentation/AppModel.kt | 18 +++++++-- .../novoda/gol/presentation/AppPresenter.kt | 15 +++----- .../com/novoda/gol/presentation/AppView.kt | 7 ++-- .../novoda/gol/presentation/BoardModelImpl.kt | 2 +- .../novoda/gol/presentation/BoardViewState.kt | 2 +- .../kotlin/com/novoda/gol/components/App.kt | 37 +++++++++---------- .../com/novoda/gol/components/pattern.kt | 2 +- 7 files changed, 44 insertions(+), 39 deletions(-) diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppModel.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppModel.kt index 37c6ca597..963cb7dc3 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppModel.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppModel.kt @@ -1,18 +1,28 @@ package com.novoda.gol.presentation +import com.novoda.gol.patterns.PatternEntity import kotlin.properties.Delegates.observable class AppModel { - private var isIdle by observable(true) { _, _, newValue -> - onSimulationStateChanged(newValue) + private var boardViewState by observable(BoardViewState(true)) { _, _, newValue -> + onBoardStateChanged(newValue) } var onSimulationStateChanged: (isIdle: Boolean) -> Unit by observable<(Boolean) -> Unit>({}) { _, _, newValue -> - newValue(isIdle) + newValue(boardViewState.isIdle) + } + + var onBoardStateChanged: (BoardViewState) -> Unit by observable<(BoardViewState) -> Unit>({}) { _, _, newValue -> + newValue(boardViewState) } fun toggleSimulation() { - isIdle = isIdle.not() + boardViewState = BoardViewState(isIdle = boardViewState.isIdle.not()) + onSimulationStateChanged(boardViewState.isIdle) + } + + fun selectPattern(pattern: PatternEntity) { + boardViewState = boardViewState.copy(selectedPattern = pattern) } } diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt index 1fbc99549..08f525f7a 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppPresenter.kt @@ -7,18 +7,15 @@ class AppPresenter { fun bind(view: AppView) { model.onSimulationStateChanged = { isIdle -> - view.controlButtonLabel = if (isIdle) "Start simulation" else "Stop Simulation" - view.patternSelectionVisibility = isIdle - view.board = view.board.copy(isIdle = isIdle) + view.renderControlButtonLabel(if (isIdle) "Start simulation" else "Stop Simulation") + view.renderPatternSelectionVisibility(visibility = isIdle) } - view.onControlButtonClicked = { - model.toggleSimulation() - } + model.onBoardStateChanged = view::renderBoard - view.onPatternSelected = { pattern -> - view.board = view.board.copy(selectedPattern = pattern) - } + view.onControlButtonClicked = model::toggleSimulation + + view.onPatternSelected = model::selectPattern } fun unbind(view: AppView) { diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt index 0f903d660..510570870 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/AppView.kt @@ -4,11 +4,10 @@ import com.novoda.gol.patterns.PatternEntity interface AppView { - var controlButtonLabel: String - var patternSelectionVisibility: Boolean - var board: BoardViewState - var onControlButtonClicked : () -> Unit var onPatternSelected: (pattern : PatternEntity) -> Unit + fun renderControlButtonLabel(controlButtonLabel: String) + fun renderPatternSelectionVisibility(visibility: Boolean) + fun renderBoard(boardViewState: BoardViewState) } diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardModelImpl.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardModelImpl.kt index ac09a999c..b8d92b997 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardModelImpl.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardModelImpl.kt @@ -34,7 +34,7 @@ class BoardModelImpl private constructor(initialBoard: BoardEntity, private val } override fun selectPattern(pattern: PatternEntity) { - if (gameLoop.isLooping() || this.pattern == pattern) { + if (gameLoop.isLooping()) { return } this.pattern = pattern diff --git a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardViewState.kt b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardViewState.kt index b11527e7e..b27a98f1e 100644 --- a/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardViewState.kt +++ b/game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardViewState.kt @@ -4,5 +4,5 @@ import com.novoda.gol.patterns.PatternEntity data class BoardViewState( val isIdle: Boolean, - val selectedPattern: PatternEntity? + val selectedPattern: PatternEntity? = null ) diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt index d3f29261d..ea4e8727a 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt @@ -12,31 +12,12 @@ import kotlinx.html.style import react.* import react.dom.div import react.dom.h2 -import kotlin.properties.Delegates.observable class App : RComponent(), AppView { override var onControlButtonClicked: () -> Unit = {} override var onPatternSelected: (pattern: PatternEntity) -> Unit = {} - override var controlButtonLabel by observable("") { _, _, newValue -> - setState { - controlButtonLabel = newValue - } - } - - override var patternSelectionVisibility by observable(true) { _, _, newValue -> - setState { - patternViewState.shouldDisplay = newValue - } - } - - override var board by observable(BoardViewState(true, null)) { _, _, newValue -> - setState { - boardViewState = newValue - } - } - private val presenter: AppPresenter = AppPresenter() override fun componentWillMount() { @@ -51,6 +32,24 @@ class App : RComponent(), AppView { patternViewState = PatternViewState(true, PatternRepository.patterns()) } + override fun renderControlButtonLabel(controlButtonLabel: String) { + setState { + this.controlButtonLabel = controlButtonLabel + } + } + + override fun renderPatternSelectionVisibility(visibility: Boolean) { + setState { + patternViewState.shouldDisplay = visibility + } + } + + override fun renderBoard(boardViewState: BoardViewState) { + setState { + this.boardViewState = boardViewState + } + } + override fun RBuilder.render(): ReactElement? = div { attrs.style = kotlinext.js.js { diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/pattern.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/pattern.kt index fb64cb5e2..97dbbc16b 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/pattern.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/pattern.kt @@ -43,4 +43,4 @@ fun RBuilder.pattern(patternEntity: PatternEntity, onPatternSelected: () -> Unit } } } -} \ No newline at end of file +} From 688c8217d367e519f1a346709b957224359f2821 Mon Sep 17 00:00:00 2001 From: Said Tahsin Dane Date: Wed, 10 Jan 2018 16:12:54 +0100 Subject: [PATCH 8/8] GoL: Simplify by just passing BoardViewState --- .../kotlin/com/novoda/gol/components/Board.kt | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt index 40eb4b72f..6bac1fca5 100644 --- a/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt +++ b/game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt @@ -40,19 +40,21 @@ class Board(boardProps: BoardProps) : RComponent(boardPr override fun BoardState.init(props: BoardProps) { presenter = BoardPresenter(50, 50) - if (props.selectedPattern != null) { - onPatternSelected.invoke(props.selectedPattern!!) + if (props.boardViewState.selectedPattern != null) { + onPatternSelected.invoke(props.boardViewState.selectedPattern!!) } } override fun componentWillReceiveProps(nextProps: BoardProps) { - if (nextProps.isIdle.not()) { + val state = nextProps.boardViewState + + if (state.isIdle.not()) { onStartSimulationClicked() } else { onStopSimulationClicked() } - if (nextProps.selectedPattern != null) { - onPatternSelected.invoke(nextProps.selectedPattern!!) + if (state.selectedPattern != null) { + onPatternSelected.invoke(state.selectedPattern!!) } } @@ -77,18 +79,12 @@ class Board(boardProps: BoardProps) : RComponent(boardPr } } -data class BoardProps(var isIdle: Boolean, var selectedPattern: PatternEntity? = null) : RProps +data class BoardProps(var boardViewState: BoardViewState) : RProps interface BoardState : RState { var boardEntity: BoardEntity } -fun RBuilder.board(board: BoardViewState) = board(board.isIdle, board.selectedPattern) - -fun RBuilder.board(isIdle: Boolean, selectedPattern: PatternEntity? = null) = child( - Board::class) { - attrs.isIdle = isIdle - attrs.selectedPattern = selectedPattern +fun RBuilder.board(board: BoardViewState) = child(Board::class) { + attrs.boardViewState = board } - -