forked from ikarenkov/Modo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
StackActions.kt
137 lines (116 loc) · 5.5 KB
/
StackActions.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package com.github.terrakok.modo.stack
import com.github.terrakok.modo.NavigationAction
import com.github.terrakok.modo.NavigationContainer
import com.github.terrakok.modo.ReducerAction
import com.github.terrakok.modo.Screen
import com.github.terrakok.modo.ScreenKey
interface StackAction : NavigationAction<StackState>
fun interface StackReducerAction : StackAction, ReducerAction<StackState>
class SetStack(val state: StackState) : StackReducerAction {
@Suppress("SpreadOperator")
constructor(screen: Screen, vararg screens: Screen) : this(
StackState(listOf(screen, *screens))
)
override fun reduce(oldState: StackState): StackState =
state
}
class Forward(val screen: Screen, vararg val screens: Screen) : StackReducerAction {
@Suppress("SpreadOperator")
override fun reduce(oldState: StackState): StackState = StackState(
oldState.stack + listOf(screen, *screens)
)
}
class Replace(val screen: Screen, vararg val screens: Screen) : StackReducerAction {
@Suppress("SpreadOperator")
override fun reduce(oldState: StackState): StackState = if (oldState.stack.isNotEmpty()) {
StackState(
oldState.stack.dropLast(1) + listOf(screen, *screens)
)
} else {
StackState(listOf(screen, *screens))
}
}
/**
* Backing to the first screen from the top of stack for which [backToCondition] is true.
* @param including - if true, then the screen for which [backToCondition] is true will be also removed.
*/
class BackTo(
val backToCondition: (pos: Int, screen: Screen) -> Boolean,
val including: Boolean = false
) : StackReducerAction {
constructor(screenKey: ScreenKey, including: Boolean = false) : this(
{ _, screen ->
screen.screenKey == screenKey
},
including
)
constructor(screenBackTo: Screen, including: Boolean = false) : this(
{ _, screen ->
screen == screenBackTo
},
including
)
override fun reduce(oldState: StackState): StackState {
val stack = oldState.stack
var foundPos = -1
for (i in stack.lastIndex downTo 0) {
if (backToCondition(i, stack[i])) {
foundPos = i
break
}
}
return if (foundPos != -1) {
StackState(oldState.stack.take(foundPos + if (including) 0 else 1))
} else {
oldState
}
}
companion object {
inline operator fun <reified T : Screen> invoke(including: Boolean = false): StackReducerAction = BackTo(
{ _, screen -> screen is T },
including
)
operator fun invoke(including: Boolean = false, condition: (pos: Int, screen: Screen) -> Boolean): StackReducerAction =
BackTo(condition, including)
}
}
class RemoveScreens(val condition: (pos: Int, screen: Screen) -> Boolean) : StackReducerAction {
override fun reduce(oldState: StackState): StackState = StackState(
oldState.stack.filterIndexed { i, screen -> !condition(i, screen) }
)
}
/**
* @param screensToDrop count of screens to drop from top of the stack
* @param canEmptyStack if true, then stack can be empty after this action
*/
class Back(
private val screensToDrop: Int = 1,
private val canEmptyStack: Boolean = false
) : StackReducerAction {
override fun reduce(oldState: StackState): StackState =
if (canEmptyStack || oldState.stack.size > 1) {
StackState(oldState.stack.dropLast(screensToDrop))
} else {
oldState
}
}
fun StackNavContainer.dispatch(action: (StackState) -> StackState) = dispatch(StackReducerAction(action))
fun NavigationContainer<StackState, StackAction>.forward(screen: Screen, vararg screens: Screen) = dispatch(Forward(screen, *screens))
fun NavigationContainer<StackState, StackAction>.replace(screen: Screen, vararg screens: Screen) = dispatch(Replace(screen, *screens))
fun NavigationContainer<StackState, StackAction>.setStack(screen: Screen, vararg screens: Screen) = dispatch(SetStack(screen, *screens))
fun NavigationContainer<StackState, StackAction>.setState(state: StackState) = dispatch(SetStack(state))
fun NavigationContainer<StackState, StackAction>.clearStack() = dispatch(SetStack(StackState()))
inline fun <reified T : Screen> NavigationContainer<StackState, StackAction>.backTo(including: Boolean = false) = dispatch(BackTo<T>(including))
fun NavigationContainer<StackState, StackAction>.backTo(screen: Screen, including: Boolean = false) = dispatch(BackTo(screen, including))
fun NavigationContainer<StackState, StackAction>.backTo(screenKey: ScreenKey, including: Boolean = false) = dispatch(BackTo(screenKey, including))
fun NavigationContainer<StackState, StackAction>.backTo(pos: Int, including: Boolean = false) = backTo(including) { backToPos, _ -> pos == backToPos }
fun NavigationContainer<StackState, StackAction>.backTo(including: Boolean = false, backToCondition: (pos: Int, screen: Screen) -> Boolean) =
dispatch(BackTo(including, backToCondition))
fun NavigationContainer<StackState, StackAction>.backToRoot() = backTo(0)
fun NavigationContainer<StackState, StackAction>.removeScreens(condition: (pos: Int, screen: Screen) -> Boolean) = dispatch(RemoveScreens(condition))
/**
* @param screensToDrop count of screens to drop from top of the stack
* @param canEmptyStack if true, then stack can be empty after this action
*/
fun NavigationContainer<StackState, StackAction>.back(screensToDrop: Int = 1, canEmptyStack: Boolean = false) =
dispatch(Back(screensToDrop, canEmptyStack))