Skip to content

Commit 60c1d8c

Browse files
authored
Merge pull request #347 from novoda/gol/model-app-component
GoL: MVP for App component
2 parents eac0b1a + 688c821 commit 60c1d8c

File tree

12 files changed

+166
-33
lines changed

12 files changed

+166
-33
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.novoda.gol
2+
3+
expect object Logger {
4+
5+
fun log(o: Any?)
6+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.novoda.gol.presentation
2+
3+
import com.novoda.gol.patterns.PatternEntity
4+
import kotlin.properties.Delegates.observable
5+
6+
class AppModel {
7+
8+
private var boardViewState by observable(BoardViewState(true)) { _, _, newValue ->
9+
onBoardStateChanged(newValue)
10+
}
11+
12+
var onSimulationStateChanged: (isIdle: Boolean) -> Unit by observable<(Boolean) -> Unit>({}) { _, _, newValue ->
13+
newValue(boardViewState.isIdle)
14+
}
15+
16+
var onBoardStateChanged: (BoardViewState) -> Unit by observable<(BoardViewState) -> Unit>({}) { _, _, newValue ->
17+
newValue(boardViewState)
18+
}
19+
20+
fun toggleSimulation() {
21+
boardViewState = BoardViewState(isIdle = boardViewState.isIdle.not())
22+
onSimulationStateChanged(boardViewState.isIdle)
23+
}
24+
25+
fun selectPattern(pattern: PatternEntity) {
26+
boardViewState = boardViewState.copy(selectedPattern = pattern)
27+
}
28+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.novoda.gol.presentation
2+
3+
class AppPresenter {
4+
5+
private val model = AppModel()
6+
7+
fun bind(view: AppView) {
8+
9+
model.onSimulationStateChanged = { isIdle ->
10+
view.renderControlButtonLabel(if (isIdle) "Start simulation" else "Stop Simulation")
11+
view.renderPatternSelectionVisibility(visibility = isIdle)
12+
}
13+
14+
model.onBoardStateChanged = view::renderBoard
15+
16+
view.onControlButtonClicked = model::toggleSimulation
17+
18+
view.onPatternSelected = model::selectPattern
19+
}
20+
21+
fun unbind(view: AppView) {
22+
model.onSimulationStateChanged = {}
23+
view.onControlButtonClicked = {}
24+
}
25+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.novoda.gol.presentation
2+
3+
import com.novoda.gol.patterns.PatternEntity
4+
5+
interface AppView {
6+
7+
var onControlButtonClicked : () -> Unit
8+
var onPatternSelected: (pattern : PatternEntity) -> Unit
9+
10+
fun renderControlButtonLabel(controlButtonLabel: String)
11+
fun renderPatternSelectionVisibility(visibility: Boolean)
12+
fun renderBoard(boardViewState: BoardViewState)
13+
}

game-of-life-multiplatform/common/src/main/kotlin/com/novoda/gol/presentation/BoardModelImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class BoardModelImpl private constructor(initialBoard: BoardEntity, private val
3434
}
3535

3636
override fun selectPattern(pattern: PatternEntity) {
37-
if (gameLoop.isLooping() || this.pattern == pattern) {
37+
if (gameLoop.isLooping()) {
3838
return
3939
}
4040
this.pattern = pattern
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.novoda.gol.presentation
2+
3+
import com.novoda.gol.patterns.PatternEntity
4+
5+
data class BoardViewState(
6+
val isIdle: Boolean,
7+
val selectedPattern: PatternEntity? = null
8+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.novoda.gol.presentation
2+
3+
import com.novoda.gol.patterns.PatternEntity
4+
5+
data class PatternViewState(
6+
var shouldDisplay: Boolean,
7+
val patternEntities: List<PatternEntity>
8+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.novoda.gol
2+
3+
actual object Logger {
4+
5+
actual fun log(o: Any?) {
6+
console.log(o)
7+
}
8+
}

game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/App.kt

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,50 @@ package com.novoda.gol.components
44

55
import com.novoda.gol.patterns.PatternEntity
66
import com.novoda.gol.patterns.PatternRepository
7+
import com.novoda.gol.presentation.AppPresenter
8+
import com.novoda.gol.presentation.AppView
9+
import com.novoda.gol.presentation.BoardViewState
10+
import com.novoda.gol.presentation.PatternViewState
711
import kotlinx.html.style
812
import react.*
913
import react.dom.div
1014
import react.dom.h2
1115

12-
class App : RComponent<RProps, State>() {
16+
class App : RComponent<RProps, State>(), AppView {
17+
18+
override var onControlButtonClicked: () -> Unit = {}
19+
override var onPatternSelected: (pattern: PatternEntity) -> Unit = {}
20+
21+
private val presenter: AppPresenter = AppPresenter()
22+
23+
override fun componentWillMount() {
24+
presenter.bind(this)
25+
}
26+
27+
override fun componentWillUnmount() {
28+
presenter.unbind(this)
29+
}
1330

1431
override fun State.init() {
15-
patternEntities = PatternRepository.patterns()
16-
isIdle = true
32+
patternViewState = PatternViewState(true, PatternRepository.patterns())
33+
}
34+
35+
override fun renderControlButtonLabel(controlButtonLabel: String) {
36+
setState {
37+
this.controlButtonLabel = controlButtonLabel
38+
}
39+
}
40+
41+
override fun renderPatternSelectionVisibility(visibility: Boolean) {
42+
setState {
43+
patternViewState.shouldDisplay = visibility
44+
}
45+
}
46+
47+
override fun renderBoard(boardViewState: BoardViewState) {
48+
setState {
49+
this.boardViewState = boardViewState
50+
}
1751
}
1852

1953
override fun RBuilder.render(): ReactElement? =
@@ -23,20 +57,18 @@ class App : RComponent<RProps, State>() {
2357
flexDirection = "column"
2458
}
2559

26-
controlButton(controlButtonLabel(), {
27-
setState {
28-
isIdle = isIdle.not()
29-
}
60+
controlButton(state.controlButtonLabel, {
61+
onControlButtonClicked()
3062
})
3163

3264
div {
3365
attrs.style = kotlinext.js.js {
3466
display = "flex"
3567
}
3668

37-
board(state.isIdle, state.selectedPattern)
69+
board(state.boardViewState)
3870

39-
if (state.isIdle) {
71+
if (state.patternViewState.shouldDisplay) {
4072

4173
div {
4274

@@ -46,26 +78,21 @@ class App : RComponent<RProps, State>() {
4678

4779
h2 { +"Choose a pattern" }
4880

49-
for (patternEntity in state.patternEntities) {
50-
pattern(patternEntity, {
51-
setState {
52-
selectedPattern = patternEntity
53-
}
81+
state.patternViewState.patternEntities.forEach { pattern ->
82+
pattern(pattern, {
83+
onPatternSelected(pattern)
5484
})
5585
}
5686
}
5787
}
5888
}
5989
}
60-
61-
private fun controlButtonLabel() = if (state.isIdle) "Start simulation" else "Stop Simulation"
62-
6390
}
6491

6592
interface State : RState {
66-
var patternEntities: List<PatternEntity>
67-
var isIdle: Boolean
68-
var selectedPattern: PatternEntity?
93+
var patternViewState: PatternViewState
94+
var boardViewState: BoardViewState
95+
var controlButtonLabel: String
6996
}
7097

7198
fun RBuilder.app() = child(App::class) {}

game-of-life-multiplatform/game-of-life-js/src/main/kotlin/com/novoda/gol/components/Board.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.novoda.gol.data.PositionEntity
77
import com.novoda.gol.patterns.PatternEntity
88
import com.novoda.gol.presentation.BoardPresenter
99
import com.novoda.gol.presentation.BoardView
10+
import com.novoda.gol.presentation.BoardViewState
1011
import kotlinext.js.js
1112
import kotlinx.html.style
1213
import react.*
@@ -39,18 +40,22 @@ class Board(boardProps: BoardProps) : RComponent<BoardProps, BoardState>(boardPr
3940
override fun BoardState.init(props: BoardProps) {
4041
presenter = BoardPresenter(50, 50)
4142

42-
if (props.selectedPattern != null) {
43-
onPatternSelected.invoke(props.selectedPattern!!)
43+
if (props.boardViewState.selectedPattern != null) {
44+
onPatternSelected.invoke(props.boardViewState.selectedPattern!!)
4445
}
4546
}
4647

4748
override fun componentWillReceiveProps(nextProps: BoardProps) {
48-
if (nextProps.isIdle.not()) {
49+
val state = nextProps.boardViewState
50+
51+
if (state.isIdle.not()) {
4952
onStartSimulationClicked()
5053
} else {
5154
onStopSimulationClicked()
5255
}
53-
onPatternSelected.invoke(nextProps.selectedPattern!!)
56+
if (state.selectedPattern != null) {
57+
onPatternSelected.invoke(state.selectedPattern!!)
58+
}
5459
}
5560

5661
override fun RBuilder.render() = renderBoard(state)
@@ -74,16 +79,12 @@ class Board(boardProps: BoardProps) : RComponent<BoardProps, BoardState>(boardPr
7479
}
7580
}
7681

77-
data class BoardProps(var isIdle: Boolean, var selectedPattern: PatternEntity? = null) : RProps
82+
data class BoardProps(var boardViewState: BoardViewState) : RProps
7883

7984
interface BoardState : RState {
8085
var boardEntity: BoardEntity
8186
}
8287

83-
fun RBuilder.board(isIdle: Boolean, selectedPattern: PatternEntity? = null) = child(
84-
Board::class) {
85-
attrs.isIdle = isIdle
86-
attrs.selectedPattern = selectedPattern
88+
fun RBuilder.board(board: BoardViewState) = child(Board::class) {
89+
attrs.boardViewState = board
8790
}
88-
89-

0 commit comments

Comments
 (0)