Merge pull request #271 from ouchadam/tech/state-submodule
screen-state submodule
This commit is contained in:
commit
4d829ac84e
|
@ -17,6 +17,9 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
|
|
|
@ -14,6 +14,9 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
|
|
|
@ -16,6 +16,9 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
|
|
|
@ -16,6 +16,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
|
|
|
@ -17,6 +17,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "screen-state"]
|
||||
path = screen-state
|
||||
url = git@github.com:ouchadam/screen-state.git
|
|
@ -163,7 +163,6 @@ ext.firebase = { dependencies, name ->
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (launchTask.contains("codeCoverageReport".toLowerCase())) {
|
||||
apply from: 'tools/coverage.gradle'
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
package app.dapk.st.design.components
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
||||
@Composable
|
||||
fun <T : Any> Spider(currentPage: SpiderPage<T>, onNavigate: (SpiderPage<out T>?) -> Unit, graph: SpiderScope.() -> Unit) {
|
||||
val pageCache = remember { mutableMapOf<Route<*>, SpiderPage<out T>>() }
|
||||
pageCache[currentPage.route] = currentPage
|
||||
|
||||
val navigateAndPopStack = {
|
||||
pageCache.remove(currentPage.route)
|
||||
onNavigate(pageCache[currentPage.parent])
|
||||
}
|
||||
val itemScope = object : SpiderItemScope {
|
||||
override fun goBack() {
|
||||
navigateAndPopStack()
|
||||
}
|
||||
}
|
||||
|
||||
val computedWeb = remember(true) {
|
||||
mutableMapOf<Route<*>, @Composable (T) -> Unit>().also { computedWeb ->
|
||||
val scope = object : SpiderScope {
|
||||
override fun <T> item(route: Route<T>, content: @Composable SpiderItemScope.(T) -> Unit) {
|
||||
computedWeb[route] = { content(itemScope, it as T) }
|
||||
}
|
||||
}
|
||||
graph.invoke(scope)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
if (currentPage.hasToolbar) {
|
||||
Toolbar(
|
||||
onNavigate = navigateAndPopStack,
|
||||
title = currentPage.label
|
||||
)
|
||||
}
|
||||
BackHandler(onBack = navigateAndPopStack)
|
||||
computedWeb[currentPage.route]!!.invoke(currentPage.state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface SpiderScope {
|
||||
fun <T> item(route: Route<T>, content: @Composable SpiderItemScope.(T) -> Unit)
|
||||
}
|
||||
|
||||
interface SpiderItemScope {
|
||||
fun goBack()
|
||||
}
|
||||
|
||||
data class SpiderPage<T>(
|
||||
val route: Route<T>,
|
||||
val label: String,
|
||||
val parent: Route<*>?,
|
||||
val state: T,
|
||||
val hasToolbar: Boolean = true,
|
||||
)
|
||||
|
||||
@JvmInline
|
||||
value class Route<out S>(val value: String)
|
|
@ -5,5 +5,4 @@ dependencies {
|
|||
implementation project(":features:navigator")
|
||||
implementation project(":design-library")
|
||||
api project(":domains:android:core")
|
||||
api project(":domains:state")
|
||||
}
|
||||
|
|
|
@ -21,53 +21,3 @@ inline fun <reified VM : ViewModel> ComponentActivity.viewModel(
|
|||
}
|
||||
return ViewModelLazy(VM::class, { viewModelStore }, { factoryPromise })
|
||||
}
|
||||
|
||||
|
||||
inline fun <reified S, E> ComponentActivity.state(
|
||||
noinline factory: () -> State<S, E>
|
||||
): Lazy<State<S, E>> {
|
||||
val factoryPromise = object : Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return when(modelClass) {
|
||||
StateViewModel::class.java -> factory() as T
|
||||
else -> throw Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
return KeyedViewModelLazy(
|
||||
key = S::class.java.canonicalName!!,
|
||||
StateViewModel::class,
|
||||
{ viewModelStore },
|
||||
{ factoryPromise }
|
||||
) as Lazy<State<S, E>>
|
||||
}
|
||||
|
||||
class KeyedViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
|
||||
private val key: String,
|
||||
private val viewModelClass: KClass<VM>,
|
||||
private val storeProducer: () -> ViewModelStore,
|
||||
private val factoryProducer: () -> ViewModelProvider.Factory,
|
||||
) : Lazy<VM> {
|
||||
private var cached: VM? = null
|
||||
|
||||
override val value: VM
|
||||
get() {
|
||||
val viewModel = cached
|
||||
return if (viewModel == null) {
|
||||
val factory = factoryProducer()
|
||||
val store = storeProducer()
|
||||
ViewModelProvider(
|
||||
store,
|
||||
factory,
|
||||
CreationExtras.Empty
|
||||
).get(key, viewModelClass.java).also {
|
||||
cached = it
|
||||
}
|
||||
} else {
|
||||
viewModel
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInitialized(): Boolean = cached != null
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package app.dapk.st.core
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.dapk.state.Action
|
||||
import app.dapk.state.ReducerFactory
|
||||
import app.dapk.state.Store
|
||||
import app.dapk.state.createStore
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
||||
class StateViewModel<S, E>(
|
||||
reducerFactory: ReducerFactory<S>,
|
||||
eventSource: MutableSharedFlow<E>,
|
||||
) : ViewModel(), State<S, E> {
|
||||
|
||||
private val store: Store<S> = createStore(reducerFactory, viewModelScope)
|
||||
override val events: SharedFlow<E> = eventSource
|
||||
override val current
|
||||
get() = _state!!
|
||||
private var _state: S by mutableStateOf(store.getState())
|
||||
|
||||
init {
|
||||
_state = store.getState()
|
||||
store.subscribe {
|
||||
_state = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispatch(action: Action) {
|
||||
store.dispatch(action)
|
||||
}
|
||||
}
|
||||
|
||||
fun <S, E> createStateViewModel(block: (suspend (E) -> Unit) -> ReducerFactory<S>): StateViewModel<S, E> {
|
||||
val eventSource = MutableSharedFlow<E>(extraBufferCapacity = 1)
|
||||
val reducer = block { eventSource.emit(it) }
|
||||
return StateViewModel(reducer, eventSource)
|
||||
}
|
||||
|
||||
interface State<S, E> {
|
||||
fun dispatch(action: Action)
|
||||
val events: SharedFlow<E>
|
||||
val current: S
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
package app.dapk.st.core.page
|
||||
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.state.*
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
sealed interface PageAction<out P> : Action {
|
||||
data class GoTo<P : Any>(val page: SpiderPage<P>) : PageAction<P>
|
||||
}
|
||||
|
||||
sealed interface PageStateChange : Action {
|
||||
data class ChangePage<P : Any>(val previous: SpiderPage<out P>, val newPage: SpiderPage<out P>) : PageAction<P>
|
||||
data class UpdatePage<P : Any>(val pageContent: P) : PageAction<P>
|
||||
}
|
||||
|
||||
data class PageContainer<P>(
|
||||
val page: SpiderPage<out P>
|
||||
)
|
||||
|
||||
interface PageReducerScope<P> {
|
||||
fun <PC : Any> withPageContent(page: KClass<PC>, block: PageDispatchScope<PC>.() -> Unit)
|
||||
fun rawPage(): SpiderPage<out P>
|
||||
}
|
||||
|
||||
interface PageDispatchScope<PC> {
|
||||
fun ReducerScope<*>.pageDispatch(action: PageAction<PC>)
|
||||
fun getPageState(): PC?
|
||||
}
|
||||
|
||||
fun <P : Any, S : Any> createPageReducer(
|
||||
initialPage: SpiderPage<out P>,
|
||||
factory: PageReducerScope<P>.() -> ReducerFactory<S>,
|
||||
): ReducerFactory<Combined2<PageContainer<P>, S>> = shareState {
|
||||
combineReducers(createPageReducer(initialPage), factory(pageReducerScope()))
|
||||
}
|
||||
|
||||
private fun <P : Any, S : Any> SharedStateScope<Combined2<PageContainer<P>, S>>.pageReducerScope() = object : PageReducerScope<P> {
|
||||
override fun <PC : Any> withPageContent(page: KClass<PC>, block: PageDispatchScope<PC>.() -> Unit) {
|
||||
val currentPage = getSharedState().state1.page.state
|
||||
if (currentPage::class == page) {
|
||||
val pageDispatchScope = object : PageDispatchScope<PC> {
|
||||
override fun ReducerScope<*>.pageDispatch(action: PageAction<PC>) {
|
||||
val currentPageGuard = getSharedState().state1.page.state
|
||||
if (currentPageGuard::class == page) {
|
||||
dispatch(action)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPageState() = getSharedState().state1.page.state as? PC
|
||||
}
|
||||
block(pageDispatchScope)
|
||||
}
|
||||
}
|
||||
|
||||
override fun rawPage() = getSharedState().state1.page
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <P : Any> createPageReducer(
|
||||
initialPage: SpiderPage<out P>
|
||||
): ReducerFactory<PageContainer<P>> {
|
||||
return createReducer(
|
||||
initialState = PageContainer(
|
||||
page = initialPage
|
||||
),
|
||||
|
||||
async(PageAction.GoTo::class) { action ->
|
||||
val state = getState()
|
||||
if (state.page.state::class != action.page.state::class) {
|
||||
dispatch(PageStateChange.ChangePage(previous = state.page, newPage = action.page))
|
||||
} else {
|
||||
dispatch(PageStateChange.UpdatePage(action.page.state))
|
||||
}
|
||||
},
|
||||
|
||||
change(PageStateChange.ChangePage::class) { action, state ->
|
||||
state.copy(page = action.newPage as SpiderPage<out P>)
|
||||
},
|
||||
|
||||
change(PageStateChange.UpdatePage::class) { action, state ->
|
||||
val isSamePage = state.page.state::class == action.pageContent::class
|
||||
if (isSamePage) {
|
||||
val updatedPageContent = (state.page as SpiderPage<Any>).copy(state = action.pageContent)
|
||||
state.copy(page = updatedPageContent as SpiderPage<out P>)
|
||||
} else {
|
||||
state
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <reified PC : Any> PageReducerScope<*>.withPageContext(crossinline block: PageDispatchScope<PC>.(PC) -> Unit) {
|
||||
withPageContent(PC::class) { getPageState()?.let { block(it) } }
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
plugins {
|
||||
id 'kotlin'
|
||||
id 'java-test-fixtures'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation Dependencies.mavenCentral.kotlinCoroutinesCore
|
||||
|
||||
testFixturesImplementation testFixtures(project(":core"))
|
||||
testFixturesImplementation Dependencies.mavenCentral.kotlinCoroutinesCore
|
||||
testFixturesImplementation Dependencies.mavenCentral.kluent
|
||||
testFixturesImplementation Dependencies.mavenCentral.mockk
|
||||
testFixturesImplementation Dependencies.mavenCentral.kotlinCoroutinesTest
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
package app.dapk.state
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
fun <S> createStore(reducerFactory: ReducerFactory<S>, coroutineScope: CoroutineScope): Store<S> {
|
||||
val subscribers = mutableListOf<(S) -> Unit>()
|
||||
var state: S = reducerFactory.initialState()
|
||||
return object : Store<S> {
|
||||
private val scope = createScope(coroutineScope, this)
|
||||
private val reducer = reducerFactory.create(scope)
|
||||
|
||||
override fun dispatch(action: Action) {
|
||||
coroutineScope.launch {
|
||||
state = reducer.reduce(action).also { nextState ->
|
||||
if (nextState != state) {
|
||||
subscribers.forEach { it.invoke(nextState) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getState() = state
|
||||
|
||||
override fun subscribe(subscriber: (S) -> Unit) {
|
||||
subscribers.add(subscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ReducerFactory<S> {
|
||||
fun create(scope: ReducerScope<S>): Reducer<S>
|
||||
fun initialState(): S
|
||||
}
|
||||
|
||||
fun interface Reducer<S> {
|
||||
fun reduce(action: Action): S
|
||||
}
|
||||
|
||||
private fun <S> createScope(coroutineScope: CoroutineScope, store: Store<S>) = object : ReducerScope<S> {
|
||||
override val coroutineScope = coroutineScope
|
||||
override fun dispatch(action: Action) = store.dispatch(action)
|
||||
override fun getState(): S = store.getState()
|
||||
}
|
||||
|
||||
interface Store<S> {
|
||||
fun dispatch(action: Action)
|
||||
fun getState(): S
|
||||
fun subscribe(subscriber: (S) -> Unit)
|
||||
}
|
||||
|
||||
interface ReducerScope<S> {
|
||||
val coroutineScope: CoroutineScope
|
||||
fun dispatch(action: Action)
|
||||
fun getState(): S
|
||||
}
|
||||
|
||||
sealed interface ActionHandler<S> {
|
||||
val key: KClass<Action>
|
||||
|
||||
class Async<S>(override val key: KClass<Action>, val handler: suspend ReducerScope<S>.(Action) -> Unit) : ActionHandler<S>
|
||||
class Sync<S>(override val key: KClass<Action>, val handler: (Action, S) -> S) : ActionHandler<S>
|
||||
class Delegate<S>(override val key: KClass<Action>, val handler: ReducerScope<S>.(Action) -> ActionHandler<S>) : ActionHandler<S>
|
||||
}
|
||||
|
||||
data class Combined2<S1, S2>(val state1: S1, val state2: S2)
|
||||
|
||||
fun interface SharedStateScope<C> {
|
||||
fun getSharedState(): C
|
||||
}
|
||||
|
||||
fun <S> shareState(block: SharedStateScope<S>.() -> ReducerFactory<S>): ReducerFactory<S> {
|
||||
var internalScope: ReducerScope<S>? = null
|
||||
val scope = SharedStateScope { internalScope!!.getState() }
|
||||
val combinedFactory = block(scope)
|
||||
return object : ReducerFactory<S> {
|
||||
override fun create(scope: ReducerScope<S>) = combinedFactory.create(scope).also { internalScope = scope }
|
||||
override fun initialState() = combinedFactory.initialState()
|
||||
}
|
||||
}
|
||||
|
||||
fun <S1, S2> combineReducers(r1: ReducerFactory<S1>, r2: ReducerFactory<S2>): ReducerFactory<Combined2<S1, S2>> {
|
||||
return object : ReducerFactory<Combined2<S1, S2>> {
|
||||
override fun create(scope: ReducerScope<Combined2<S1, S2>>): Reducer<Combined2<S1, S2>> {
|
||||
val r1Scope = createReducerScope(scope) { scope.getState().state1 }
|
||||
val r2Scope = createReducerScope(scope) { scope.getState().state2 }
|
||||
|
||||
val r1Reducer = r1.create(r1Scope)
|
||||
val r2Reducer = r2.create(r2Scope)
|
||||
return Reducer {
|
||||
Combined2(r1Reducer.reduce(it), r2Reducer.reduce(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun initialState(): Combined2<S1, S2> = Combined2(r1.initialState(), r2.initialState())
|
||||
}
|
||||
}
|
||||
|
||||
private fun <S> createReducerScope(scope: ReducerScope<*>, state: () -> S) = object : ReducerScope<S> {
|
||||
override val coroutineScope: CoroutineScope = scope.coroutineScope
|
||||
override fun dispatch(action: Action) = scope.dispatch(action)
|
||||
override fun getState() = state.invoke()
|
||||
}
|
||||
|
||||
fun <S> createReducer(
|
||||
initialState: S,
|
||||
vararg reducers: (ReducerScope<S>) -> ActionHandler<S>,
|
||||
): ReducerFactory<S> {
|
||||
return object : ReducerFactory<S> {
|
||||
override fun create(scope: ReducerScope<S>): Reducer<S> {
|
||||
val reducersMap = reducers
|
||||
.map { it.invoke(scope) }
|
||||
.groupBy { it.key }
|
||||
|
||||
return Reducer { action ->
|
||||
val result = reducersMap.keys
|
||||
.filter { it.java.isAssignableFrom(action::class.java) }
|
||||
.fold(scope.getState()) { acc, key ->
|
||||
val actionHandlers = reducersMap[key]!!
|
||||
actionHandlers.fold(acc) { acc, handler ->
|
||||
when (handler) {
|
||||
is ActionHandler.Async -> {
|
||||
scope.coroutineScope.launch {
|
||||
handler.handler.invoke(scope, action)
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
is ActionHandler.Sync -> handler.handler.invoke(action, acc)
|
||||
is ActionHandler.Delegate -> when (val next = handler.handler.invoke(scope, action)) {
|
||||
is ActionHandler.Async -> {
|
||||
scope.coroutineScope.launch {
|
||||
next.handler.invoke(scope, action)
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
is ActionHandler.Sync -> next.handler.invoke(action, acc)
|
||||
is ActionHandler.Delegate -> error("is not possible")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
override fun initialState(): S = initialState
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun <A : Action, S> sideEffect(klass: KClass<A>, block: suspend (A, S) -> Unit): (ReducerScope<S>) -> ActionHandler<S> {
|
||||
return {
|
||||
ActionHandler.Async(key = klass as KClass<Action>) { action -> block(action as A, getState()) }
|
||||
}
|
||||
}
|
||||
|
||||
fun <A : Action, S> change(klass: KClass<A>, block: (A, S) -> S): (ReducerScope<S>) -> ActionHandler<S> {
|
||||
return {
|
||||
ActionHandler.Sync(key = klass as KClass<Action>, block as (Action, S) -> S)
|
||||
}
|
||||
}
|
||||
|
||||
fun <A : Action, S> async(klass: KClass<A>, block: suspend ReducerScope<S>.(A) -> Unit): (ReducerScope<S>) -> ActionHandler<S> {
|
||||
return {
|
||||
ActionHandler.Async(key = klass as KClass<Action>, block as suspend ReducerScope<S>.(Action) -> Unit)
|
||||
}
|
||||
}
|
||||
|
||||
fun <A : Action, S> multi(klass: KClass<A>, block: Multi<A, S>.(A) -> (ReducerScope<S>) -> ActionHandler<S>): (ReducerScope<S>) -> ActionHandler<S> {
|
||||
val multiScope = object : Multi<A, S> {
|
||||
override fun sideEffect(block: suspend (S) -> Unit): (ReducerScope<S>) -> ActionHandler<S> = sideEffect(klass) { _, state -> block(state) }
|
||||
override fun change(block: (A, S) -> S): (ReducerScope<S>) -> ActionHandler<S> = change(klass, block)
|
||||
override fun async(block: suspend ReducerScope<S>.(A) -> Unit): (ReducerScope<S>) -> ActionHandler<S> = async(klass, block)
|
||||
override fun nothing() = sideEffect { }
|
||||
}
|
||||
|
||||
return {
|
||||
ActionHandler.Delegate(key = klass as KClass<Action>) { action ->
|
||||
block(multiScope, action as A).invoke(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Multi<A : Action, S> {
|
||||
fun sideEffect(block: suspend (S) -> Unit): (ReducerScope<S>) -> ActionHandler<S>
|
||||
fun nothing(): (ReducerScope<S>) -> ActionHandler<S>
|
||||
fun change(block: (A, S) -> S): (ReducerScope<S>) -> ActionHandler<S>
|
||||
fun async(block: suspend ReducerScope<S>.(A) -> Unit): (ReducerScope<S>) -> ActionHandler<S>
|
||||
}
|
||||
|
||||
interface Action
|
|
@ -1,20 +0,0 @@
|
|||
package fake
|
||||
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
|
||||
class FakeEventSource<E> : (E) -> Unit {
|
||||
|
||||
private val captures = mutableListOf<E>()
|
||||
|
||||
override fun invoke(event: E) {
|
||||
captures.add(event)
|
||||
}
|
||||
|
||||
fun assertEvents(expected: List<E>) {
|
||||
assertEquals(expected, captures)
|
||||
}
|
||||
|
||||
fun assertNoEvents() {
|
||||
assertEquals(emptyList(), captures)
|
||||
}
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
package test
|
||||
|
||||
import app.dapk.state.Action
|
||||
import app.dapk.state.Reducer
|
||||
import app.dapk.state.ReducerFactory
|
||||
import app.dapk.state.ReducerScope
|
||||
import fake.FakeEventSource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
|
||||
interface ReducerTest<S, E> {
|
||||
operator fun invoke(block: suspend ReducerTestScope<S, E>.() -> Unit)
|
||||
}
|
||||
|
||||
fun <S, E> testReducer(block: ((E) -> Unit) -> ReducerFactory<S>): ReducerTest<S, E> {
|
||||
val fakeEventSource = FakeEventSource<E>()
|
||||
val reducerFactory = block(fakeEventSource)
|
||||
return object : ReducerTest<S, E> {
|
||||
override fun invoke(block: suspend ReducerTestScope<S, E>.() -> Unit) {
|
||||
runReducerTest(reducerFactory, fakeEventSource, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <S, E> runReducerTest(reducerFactory: ReducerFactory<S>, fakeEventSource: FakeEventSource<E>, block: suspend ReducerTestScope<S, E>.() -> Unit) {
|
||||
runTest {
|
||||
val expectTestScope = ExpectTest(coroutineContext)
|
||||
block(ReducerTestScope(reducerFactory, fakeEventSource, expectTestScope))
|
||||
expectTestScope.verifyExpects()
|
||||
}
|
||||
}
|
||||
|
||||
class ReducerTestScope<S, E>(
|
||||
private val reducerFactory: ReducerFactory<S>,
|
||||
private val fakeEventSource: FakeEventSource<E>,
|
||||
private val expectTestScope: ExpectTestScope
|
||||
) : ExpectTestScope by expectTestScope, Reducer<S> {
|
||||
|
||||
private var invalidateCapturedState: Boolean = false
|
||||
private val actionSideEffects = mutableMapOf<Action, () -> S>()
|
||||
private var manualState: S? = null
|
||||
private var capturedResult: S? = null
|
||||
|
||||
private val actionCaptures = mutableListOf<Action>()
|
||||
private val reducerScope = object : ReducerScope<S> {
|
||||
override val coroutineScope = CoroutineScope(UnconfinedTestDispatcher())
|
||||
override fun dispatch(action: Action) {
|
||||
actionCaptures.add(action)
|
||||
|
||||
if (actionSideEffects.containsKey(action)) {
|
||||
setState(actionSideEffects.getValue(action).invoke(), invalidateCapturedState = true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getState() = manualState ?: reducerFactory.initialState()
|
||||
}
|
||||
private val reducer: Reducer<S> = reducerFactory.create(reducerScope)
|
||||
|
||||
override fun reduce(action: Action) = reducer.reduce(action).also {
|
||||
capturedResult = if (invalidateCapturedState) manualState else it
|
||||
}
|
||||
|
||||
fun actionSideEffect(action: Action, handler: () -> S) {
|
||||
actionSideEffects[action] = handler
|
||||
}
|
||||
|
||||
fun setState(state: S, invalidateCapturedState: Boolean = false) {
|
||||
manualState = state
|
||||
this.invalidateCapturedState = invalidateCapturedState
|
||||
}
|
||||
|
||||
fun setState(block: (S) -> S) {
|
||||
setState(block(reducerScope.getState()))
|
||||
}
|
||||
|
||||
fun assertInitialState(expected: S) {
|
||||
reducerFactory.initialState() shouldBeEqualTo expected
|
||||
}
|
||||
|
||||
fun assertEvents(events: List<E>) {
|
||||
fakeEventSource.assertEvents(events)
|
||||
}
|
||||
|
||||
fun assertOnlyStateChange(expected: S) {
|
||||
assertStateChange(expected)
|
||||
assertNoDispatches()
|
||||
fakeEventSource.assertNoEvents()
|
||||
}
|
||||
|
||||
fun assertOnlyStateChange(block: (S) -> S) {
|
||||
val expected = block(reducerScope.getState())
|
||||
assertStateChange(expected)
|
||||
assertNoDispatches()
|
||||
fakeEventSource.assertNoEvents()
|
||||
}
|
||||
|
||||
fun assertStateChange(expected: S) {
|
||||
capturedResult shouldBeEqualTo expected
|
||||
}
|
||||
|
||||
fun assertDispatches(expected: List<Action>) {
|
||||
assertEquals(expected, actionCaptures)
|
||||
}
|
||||
|
||||
fun assertNoDispatches() {
|
||||
assertEquals(emptyList(), actionCaptures)
|
||||
}
|
||||
|
||||
fun assertNoStateChange() {
|
||||
assertEquals(reducerScope.getState(), capturedResult)
|
||||
}
|
||||
|
||||
fun assertNoEvents() {
|
||||
fakeEventSource.assertNoEvents()
|
||||
}
|
||||
|
||||
fun assertOnlyDispatches(expected: List<Action>) {
|
||||
assertDispatches(expected)
|
||||
fakeEventSource.assertNoEvents()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
fun assertOnlyEvents(events: List<E>) {
|
||||
fakeEventSource.assertEvents(events)
|
||||
assertNoDispatches()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
fun assertNoChanges() {
|
||||
assertNoStateChange()
|
||||
assertNoEvents()
|
||||
assertNoDispatches()
|
||||
}
|
||||
}
|
||||
|
||||
fun <S, E> ReducerTestScope<S, E>.assertOnlyDispatches(vararg action: Action) {
|
||||
this.assertOnlyDispatches(action.toList())
|
||||
}
|
||||
|
||||
fun <S, E> ReducerTestScope<S, E>.assertDispatches(vararg action: Action) {
|
||||
this.assertDispatches(action.toList())
|
||||
}
|
||||
|
||||
fun <S, E> ReducerTestScope<S, E>.assertEvents(vararg event: E) {
|
||||
this.assertEvents(event.toList())
|
||||
}
|
||||
|
||||
fun <S, E> ReducerTestScope<S, E>.assertOnlyEvents(vararg event: E) {
|
||||
this.assertOnlyEvents(event.toList())
|
||||
}
|
|
@ -3,7 +3,7 @@ applyAndroidComposeLibraryModule(project)
|
|||
dependencies {
|
||||
implementation project(":chat-engine")
|
||||
implementation project(":domains:android:compose-core")
|
||||
implementation project(":domains:state")
|
||||
implementation 'screen-state:screen-android'
|
||||
implementation project(":features:messenger")
|
||||
implementation project(":core")
|
||||
implementation project(":design-library")
|
||||
|
@ -11,9 +11,9 @@ dependencies {
|
|||
|
||||
kotlinTest(it)
|
||||
|
||||
testImplementation 'screen-state:state-test'
|
||||
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
||||
androidImportFixturesWorkaround(project, project(":core"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:state"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:store"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
||||
androidImportFixturesWorkaround(project, project(":chat-engine"))
|
||||
|
|
|
@ -2,7 +2,7 @@ package app.dapk.st.directory
|
|||
|
||||
import android.content.Context
|
||||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.core.createStateViewModel
|
||||
import app.dapk.st.state.createStateViewModel
|
||||
import app.dapk.st.core.JobBag
|
||||
import app.dapk.st.directory.state.DirectoryState
|
||||
import app.dapk.st.directory.state.directoryReducer
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package app.dapk.st.directory.state
|
||||
|
||||
import app.dapk.st.core.State
|
||||
import app.dapk.st.state.State
|
||||
import app.dapk.st.engine.DirectoryState
|
||||
|
||||
typealias DirectoryState = State<DirectoryScreenState, DirectoryEvent>
|
||||
|
|
|
@ -9,7 +9,7 @@ dependencies {
|
|||
implementation project(":domains:android:compose-core")
|
||||
implementation project(":domains:android:viewmodel")
|
||||
implementation project(':domains:store')
|
||||
implementation project(':domains:state')
|
||||
implementation 'screen-state:screen-android'
|
||||
implementation project(":core")
|
||||
implementation project(":design-library")
|
||||
implementation Dependencies.mavenCentral.coil
|
||||
|
|
|
@ -13,11 +13,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import app.dapk.st.core.DapkActivity
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.core.state
|
||||
import app.dapk.st.core.viewModel
|
||||
import app.dapk.st.directory.DirectoryModule
|
||||
import app.dapk.st.login.LoginModule
|
||||
import app.dapk.st.profile.ProfileModule
|
||||
import app.dapk.st.state.state
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ dependencies {
|
|||
implementation project(":domains:android:compose-core")
|
||||
implementation project(":domains:android:viewmodel")
|
||||
implementation project(":domains:store")
|
||||
implementation project(":domains:state")
|
||||
implementation 'screen-state:screen-android'
|
||||
implementation project(":core")
|
||||
implementation project(":features:navigator")
|
||||
implementation project(":design-library")
|
||||
|
@ -14,10 +14,10 @@ dependencies {
|
|||
|
||||
kotlinTest(it)
|
||||
|
||||
testImplementation 'screen-state:state-test'
|
||||
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
||||
androidImportFixturesWorkaround(project, project(":core"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:store"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:state"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:android:viewmodel"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
||||
androidImportFixturesWorkaround(project, project(":chat-engine"))
|
||||
|
|
|
@ -10,15 +10,17 @@ import androidx.compose.material3.Surface
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import app.dapk.st.core.*
|
||||
import app.dapk.st.core.AndroidUri
|
||||
import app.dapk.st.core.DapkActivity
|
||||
import app.dapk.st.core.MimeType
|
||||
import app.dapk.st.core.extensions.unsafeLazy
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.messenger.gallery.GetImageFromGallery
|
||||
import app.dapk.st.messenger.state.ComposerStateChange
|
||||
import app.dapk.st.messenger.state.MessengerEvent
|
||||
import app.dapk.st.messenger.state.MessengerScreenState
|
||||
import app.dapk.st.messenger.state.MessengerState
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
import app.dapk.st.state.state
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
|
@ -27,7 +29,7 @@ val LocalImageRequestFactory = staticCompositionLocalOf<ImageRequest.Builder> {
|
|||
class MessengerActivity : DapkActivity() {
|
||||
|
||||
private val module by unsafeLazy { module<MessengerModule>() }
|
||||
private val state by state { module.messengerState(readPayload()) }
|
||||
private val state: MessengerState by state { module.messengerState(readPayload()) }
|
||||
|
||||
companion object {
|
||||
|
||||
|
@ -87,4 +89,4 @@ data class MessagerActivityPayload(
|
|||
|
||||
fun <T : Parcelable> Activity.readPayload(): T = intent.getParcelableExtra("key") ?: intent.getStringExtra("shortcut_key")!!.let {
|
||||
MessagerActivityPayload(it) as T
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import android.content.Context
|
|||
import app.dapk.st.core.DeviceMeta
|
||||
import app.dapk.st.core.JobBag
|
||||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.core.createStateViewModel
|
||||
import app.dapk.st.state.createStateViewModel
|
||||
import app.dapk.st.domain.application.message.MessageOptionsStore
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
|
|
|
@ -15,16 +15,17 @@ import androidx.compose.runtime.State
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import app.dapk.st.core.*
|
||||
import app.dapk.st.core.extensions.unsafeLazy
|
||||
import app.dapk.st.design.components.GenericError
|
||||
import app.dapk.st.messenger.gallery.state.ImageGalleryState
|
||||
import app.dapk.st.state.state
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class ImageGalleryActivity : DapkActivity() {
|
||||
|
||||
private val module by unsafeLazy { module<ImageGalleryModule>() }
|
||||
private val imageGalleryState by state {
|
||||
private val imageGalleryState: ImageGalleryState by state {
|
||||
val payload = intent.getParcelableExtra("key") as? ImageGalleryActivityPayload
|
||||
val module = module<ImageGalleryModule>()
|
||||
module.imageGalleryState(payload!!.roomName)
|
||||
}
|
||||
|
||||
|
@ -94,4 +95,4 @@ class GetImageFromGallery : ActivityResultContract<ImageGalleryActivityPayload,
|
|||
@Parcelize
|
||||
data class ImageGalleryActivityPayload(
|
||||
val roomName: String,
|
||||
) : Parcelable
|
||||
) : Parcelable
|
||||
|
|
|
@ -6,7 +6,7 @@ import android.provider.MediaStore
|
|||
import app.dapk.st.core.CoroutineDispatchers
|
||||
import app.dapk.st.core.JobBag
|
||||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.core.createStateViewModel
|
||||
import app.dapk.st.state.createStateViewModel
|
||||
import app.dapk.st.messenger.gallery.state.ImageGalleryState
|
||||
import app.dapk.st.messenger.gallery.state.imageGalleryReducer
|
||||
|
||||
|
|
|
@ -21,13 +21,14 @@ import androidx.compose.ui.unit.sp
|
|||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.LifecycleEffect
|
||||
import app.dapk.st.core.components.CenteredLoading
|
||||
import app.dapk.st.core.page.PageAction
|
||||
import app.dapk.st.design.components.GenericError
|
||||
import app.dapk.st.design.components.Spider
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.design.components.Toolbar
|
||||
import app.dapk.st.messenger.gallery.state.ImageGalleryActions
|
||||
import app.dapk.st.messenger.gallery.state.ImageGalleryPage
|
||||
import app.dapk.st.messenger.gallery.state.ImageGalleryState
|
||||
import app.dapk.state.SpiderPage
|
||||
import app.dapk.state.page.PageAction
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.request.ImageRequest
|
||||
|
||||
|
@ -44,7 +45,7 @@ fun ImageGalleryScreen(state: ImageGalleryState, onTopLevelBack: () -> Unit, onI
|
|||
}
|
||||
}
|
||||
|
||||
Spider(currentPage = state.current.state1.page, onNavigate = onNavigate) {
|
||||
Spider(currentPage = state.current.state1.page, onNavigate = onNavigate, toolbar = { navigate, title -> Toolbar(navigate, title) }) {
|
||||
item(ImageGalleryPage.Routes.folders) {
|
||||
ImageGalleryFolders(
|
||||
it,
|
||||
|
|
|
@ -2,15 +2,15 @@ package app.dapk.st.messenger.gallery.state
|
|||
|
||||
import app.dapk.st.core.JobBag
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.page.PageAction
|
||||
import app.dapk.st.core.page.PageStateChange
|
||||
import app.dapk.st.core.page.createPageReducer
|
||||
import app.dapk.st.core.page.withPageContext
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.messenger.gallery.FetchMediaFoldersUseCase
|
||||
import app.dapk.st.messenger.gallery.FetchMediaUseCase
|
||||
import app.dapk.state.SpiderPage
|
||||
import app.dapk.state.async
|
||||
import app.dapk.state.createReducer
|
||||
import app.dapk.state.page.PageAction
|
||||
import app.dapk.state.page.PageStateChange
|
||||
import app.dapk.state.page.createPageReducer
|
||||
import app.dapk.state.page.withPageContext
|
||||
import app.dapk.state.sideEffect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package app.dapk.st.messenger.gallery.state
|
||||
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.State
|
||||
import app.dapk.st.design.components.Route
|
||||
import app.dapk.st.state.State
|
||||
import app.dapk.st.messenger.gallery.Folder
|
||||
import app.dapk.st.messenger.gallery.Media
|
||||
import app.dapk.st.core.page.PageContainer
|
||||
import app.dapk.state.Combined2
|
||||
import app.dapk.state.Route
|
||||
import app.dapk.state.page.PageContainer
|
||||
|
||||
typealias ImageGalleryState = State<Combined2<PageContainer<ImageGalleryPage>, Unit>, Unit>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package app.dapk.st.messenger.state
|
||||
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.State
|
||||
import app.dapk.st.state.State
|
||||
import app.dapk.st.design.components.BubbleModel
|
||||
import app.dapk.st.engine.MessengerPageState
|
||||
import app.dapk.st.engine.RoomEvent
|
||||
|
|
|
@ -2,15 +2,15 @@ package app.dapk.st.messenger.gallery.state
|
|||
|
||||
import android.net.Uri
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.page.PageAction
|
||||
import app.dapk.st.core.page.PageContainer
|
||||
import app.dapk.st.core.page.PageStateChange
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.messenger.gallery.FetchMediaFoldersUseCase
|
||||
import app.dapk.st.messenger.gallery.FetchMediaUseCase
|
||||
import app.dapk.st.messenger.gallery.Folder
|
||||
import app.dapk.st.messenger.gallery.Media
|
||||
import app.dapk.state.Combined2
|
||||
import app.dapk.state.SpiderPage
|
||||
import app.dapk.state.page.PageAction
|
||||
import app.dapk.state.page.PageContainer
|
||||
import app.dapk.state.page.PageStateChange
|
||||
import fake.FakeJobBag
|
||||
import fake.FakeUri
|
||||
import io.mockk.coEvery
|
||||
|
@ -18,7 +18,6 @@ import io.mockk.mockk
|
|||
import org.junit.Test
|
||||
import test.assertOnlyDispatches
|
||||
import test.delegateReturn
|
||||
import test.expect
|
||||
import test.testReducer
|
||||
|
||||
private const val A_ROOM_NAME = "a room name"
|
||||
|
|
|
@ -4,16 +4,16 @@ dependencies {
|
|||
implementation project(":chat-engine")
|
||||
implementation project(":features:settings")
|
||||
implementation project(':domains:store')
|
||||
implementation project(':domains:state')
|
||||
implementation 'screen-state:screen-android'
|
||||
implementation project(":domains:android:compose-core")
|
||||
implementation project(":design-library")
|
||||
implementation project(":core")
|
||||
|
||||
kotlinTest(it)
|
||||
|
||||
testImplementation 'screen-state:state-test'
|
||||
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
||||
androidImportFixturesWorkaround(project, project(":core"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:state"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:store"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
||||
androidImportFixturesWorkaround(project, project(":chat-engine"))
|
||||
|
|
|
@ -2,7 +2,7 @@ package app.dapk.st.profile
|
|||
|
||||
import app.dapk.st.core.JobBag
|
||||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.core.createStateViewModel
|
||||
import app.dapk.st.state.createStateViewModel
|
||||
import app.dapk.st.core.extensions.ErrorTracker
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.st.profile.state.ProfileState
|
||||
|
|
|
@ -20,7 +20,6 @@ import androidx.compose.ui.unit.dp
|
|||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.LifecycleEffect
|
||||
import app.dapk.st.core.components.CenteredLoading
|
||||
import app.dapk.st.core.page.PageAction
|
||||
import app.dapk.st.design.components.*
|
||||
import app.dapk.st.engine.RoomInvite
|
||||
import app.dapk.st.engine.RoomInvite.InviteMeta
|
||||
|
@ -28,6 +27,8 @@ import app.dapk.st.profile.state.Page
|
|||
import app.dapk.st.profile.state.ProfileAction
|
||||
import app.dapk.st.profile.state.ProfileState
|
||||
import app.dapk.st.settings.SettingsActivity
|
||||
import app.dapk.state.SpiderPage
|
||||
import app.dapk.state.page.PageAction
|
||||
|
||||
@Composable
|
||||
fun ProfileScreen(viewModel: ProfileState, onTopLevelBack: () -> Unit) {
|
||||
|
@ -47,7 +48,7 @@ fun ProfileScreen(viewModel: ProfileState, onTopLevelBack: () -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
Spider(currentPage = viewModel.current.state1.page, onNavigate = onNavigate) {
|
||||
Spider(currentPage = viewModel.current.state1.page, onNavigate = onNavigate, toolbar = { navigate, title -> Toolbar(navigate, title) }) {
|
||||
item(Page.Routes.profile) {
|
||||
ProfilePage(context, viewModel, it)
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@ package app.dapk.st.profile.state
|
|||
import app.dapk.st.core.JobBag
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.extensions.ErrorTracker
|
||||
import app.dapk.st.core.page.PageAction
|
||||
import app.dapk.st.core.page.PageStateChange
|
||||
import app.dapk.st.core.page.createPageReducer
|
||||
import app.dapk.st.core.page.withPageContext
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.state.SpiderPage
|
||||
import app.dapk.state.async
|
||||
import app.dapk.state.createReducer
|
||||
import app.dapk.state.page.PageAction
|
||||
import app.dapk.state.page.PageStateChange
|
||||
import app.dapk.state.page.createPageReducer
|
||||
import app.dapk.state.page.withPageContext
|
||||
import app.dapk.state.sideEffect
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package app.dapk.st.profile.state
|
||||
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.State
|
||||
import app.dapk.st.core.page.PageContainer
|
||||
import app.dapk.st.design.components.Route
|
||||
import app.dapk.st.state.State
|
||||
import app.dapk.st.engine.Me
|
||||
import app.dapk.st.engine.RoomInvite
|
||||
import app.dapk.state.Combined2
|
||||
import app.dapk.state.Route
|
||||
import app.dapk.state.page.PageContainer
|
||||
|
||||
typealias ProfileState = State<Combined2<PageContainer<Page>, Unit>, Unit>
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package app.dapk.st.profile.state
|
||||
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.page.PageAction
|
||||
import app.dapk.st.core.page.PageContainer
|
||||
import app.dapk.st.core.page.PageStateChange
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.engine.Me
|
||||
import app.dapk.st.matrix.common.HomeServerUrl
|
||||
import app.dapk.state.Combined2
|
||||
import app.dapk.state.SpiderPage
|
||||
import app.dapk.state.page.PageAction
|
||||
import app.dapk.state.page.PageContainer
|
||||
import app.dapk.state.page.PageStateChange
|
||||
import fake.FakeChatEngine
|
||||
import fake.FakeErrorTracker
|
||||
import fake.FakeJobBag
|
||||
|
|
|
@ -7,15 +7,16 @@ dependencies {
|
|||
implementation project(':domains:android:push')
|
||||
implementation project(":domains:android:compose-core")
|
||||
implementation project(":domains:android:viewmodel")
|
||||
implementation 'screen-state:screen-android'
|
||||
implementation project(":design-library")
|
||||
implementation project(":core")
|
||||
|
||||
kotlinTest(it)
|
||||
|
||||
testImplementation 'screen-state:state-test'
|
||||
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
||||
androidImportFixturesWorkaround(project, project(":core"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:store"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:state"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:android:viewmodel"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
||||
androidImportFixturesWorkaround(project, project(":chat-engine"))
|
||||
|
|
|
@ -4,11 +4,15 @@ import android.os.Bundle
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.ui.Modifier
|
||||
import app.dapk.st.core.*
|
||||
import app.dapk.st.core.DapkActivity
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.core.resetModules
|
||||
import app.dapk.st.settings.state.SettingsState
|
||||
import app.dapk.st.state.state
|
||||
|
||||
class SettingsActivity : DapkActivity() {
|
||||
|
||||
private val settingsState by state { module<SettingsModule>().settingsState() }
|
||||
private val settingsState: SettingsState by state { module<SettingsModule>().settingsState() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
@ -10,6 +10,7 @@ import app.dapk.st.push.PushModule
|
|||
import app.dapk.st.settings.eventlogger.EventLoggerViewModel
|
||||
import app.dapk.st.settings.state.SettingsState
|
||||
import app.dapk.st.settings.state.settingsReducer
|
||||
import app.dapk.st.state.createStateViewModel
|
||||
|
||||
class SettingsModule(
|
||||
private val chatEngine: ChatEngine,
|
||||
|
|
|
@ -41,7 +41,6 @@ import app.dapk.st.core.components.CenteredLoading
|
|||
import app.dapk.st.core.components.Header
|
||||
import app.dapk.st.core.extensions.takeAs
|
||||
import app.dapk.st.core.getActivity
|
||||
import app.dapk.st.core.page.PageAction
|
||||
import app.dapk.st.design.components.*
|
||||
import app.dapk.st.engine.ImportResult
|
||||
import app.dapk.st.navigator.Navigator
|
||||
|
@ -51,6 +50,8 @@ import app.dapk.st.settings.state.ComponentLifecycle
|
|||
import app.dapk.st.settings.state.RootActions
|
||||
import app.dapk.st.settings.state.ScreenAction
|
||||
import app.dapk.st.settings.state.SettingsState
|
||||
import app.dapk.state.SpiderPage
|
||||
import app.dapk.state.page.PageAction
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
@ -66,7 +67,7 @@ internal fun SettingsScreen(settingsState: SettingsState, onSignOut: () -> Unit,
|
|||
else -> settingsState.dispatch(PageAction.GoTo(it))
|
||||
}
|
||||
}
|
||||
Spider(currentPage = settingsState.current.state1.page, onNavigate = onNavigate) {
|
||||
Spider(currentPage = settingsState.current.state1.page, onNavigate = onNavigate, toolbar = { navigate, title -> Toolbar(navigate, title) }) {
|
||||
item(Page.Routes.root) {
|
||||
RootSettings(
|
||||
it,
|
||||
|
|
|
@ -2,10 +2,10 @@ package app.dapk.st.settings
|
|||
|
||||
import android.net.Uri
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.design.components.Route
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.engine.ImportResult
|
||||
import app.dapk.st.push.Registrar
|
||||
import app.dapk.state.Route
|
||||
import app.dapk.state.SpiderPage
|
||||
|
||||
internal data class SettingsScreenState(
|
||||
val page: SpiderPage<out Page>,
|
||||
|
@ -26,9 +26,9 @@ internal sealed interface Page {
|
|||
|
||||
object Routes {
|
||||
val root = Route<Root>("Settings")
|
||||
val encryption = Route<Page.Security>("Encryption")
|
||||
val pushProviders = Route<Page.PushProviders>("PushProviders")
|
||||
val importRoomKeys = Route<Page.ImportRoomKey>("ImportRoomKey")
|
||||
val encryption = Route<Security>("Encryption")
|
||||
val pushProviders = Route<PushProviders>("PushProviders")
|
||||
val importRoomKeys = Route<ImportRoomKey>("ImportRoomKey")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,8 @@ package app.dapk.st.settings.state
|
|||
import android.content.ContentResolver
|
||||
import app.dapk.st.core.JobBag
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.State
|
||||
import app.dapk.st.state.State
|
||||
import app.dapk.st.core.ThemeStore
|
||||
import app.dapk.st.core.page.*
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.domain.StoreCleaner
|
||||
import app.dapk.st.domain.application.eventlog.LoggingStore
|
||||
import app.dapk.st.domain.application.message.MessageOptionsStore
|
||||
|
@ -16,10 +14,8 @@ import app.dapk.st.push.PushTokenRegistrars
|
|||
import app.dapk.st.settings.*
|
||||
import app.dapk.st.settings.SettingItem.Id.*
|
||||
import app.dapk.st.settings.SettingsEvent.*
|
||||
import app.dapk.state.Combined2
|
||||
import app.dapk.state.async
|
||||
import app.dapk.state.createReducer
|
||||
import app.dapk.state.multi
|
||||
import app.dapk.state.*
|
||||
import app.dapk.state.page.*
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
package app.dapk.st.settings
|
||||
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.page.PageAction
|
||||
import app.dapk.st.core.page.PageContainer
|
||||
import app.dapk.st.core.page.PageStateChange
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.engine.ImportResult
|
||||
import app.dapk.st.push.Registrar
|
||||
import app.dapk.st.settings.state.ComponentLifecycle
|
||||
|
@ -12,6 +8,10 @@ import app.dapk.st.settings.state.RootActions
|
|||
import app.dapk.st.settings.state.ScreenAction
|
||||
import app.dapk.st.settings.state.settingsReducer
|
||||
import app.dapk.state.Combined2
|
||||
import app.dapk.state.SpiderPage
|
||||
import app.dapk.state.page.PageAction
|
||||
import app.dapk.state.page.PageContainer
|
||||
import app.dapk.state.page.PageStateChange
|
||||
import fake.*
|
||||
import fixture.aRoomId
|
||||
import internalfake.FakeSettingsItemFactory
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package internalfixture
|
||||
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.settings.Page
|
||||
import app.dapk.state.SpiderPage
|
||||
|
||||
internal fun aImportRoomKeysPage(
|
||||
state: Page.ImportRoomKey = Page.ImportRoomKey()
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 4e92f14031cc8be907cba09b3bfc1d9dbd380072
|
|
@ -6,6 +6,9 @@ dependencyResolutionManagement {
|
|||
}
|
||||
}
|
||||
rootProject.name = "SmallTalk"
|
||||
|
||||
includeBuild 'screen-state'
|
||||
|
||||
include ':app'
|
||||
|
||||
include ':design-library'
|
||||
|
|
Loading…
Reference in New Issue