mirror of
https://github.com/ouchadam/small-talk.git
synced 2025-02-25 00:17:42 +01:00
Merge pull request #298 from ouchadam/tech/home-reducer
Tech/home reducer
This commit is contained in:
commit
bfc4e83ee7
@ -67,7 +67,7 @@ class SmallTalkApplication : Application(), ModuleProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ProvidableModule> provide(klass: KClass<T>): T {
|
override fun <T : ProvidableModule> provide(klass: KClass<T>): T {
|
||||||
return when (klass) {
|
return when (klass) {
|
||||||
DirectoryModule::class -> featureModules.directoryModule
|
DirectoryModule::class -> featureModules.directoryModule
|
||||||
|
@ -190,6 +190,9 @@ internal class FeatureModules internal constructor(
|
|||||||
storeModule.value.applicationStore(),
|
storeModule.value.applicationStore(),
|
||||||
buildMeta,
|
buildMeta,
|
||||||
),
|
),
|
||||||
|
profileModule,
|
||||||
|
loginModule,
|
||||||
|
directoryModule
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val settingsModule by unsafeLazy {
|
val settingsModule by unsafeLazy {
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit cdf3e1bffba4b69dd8f752c6cc7588b0e89a17af
|
Subproject commit 9017fe3963754199db7c2525ba38a3265ef5701d
|
@ -11,6 +11,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
|||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@ -27,6 +28,10 @@ class StartScope(private val scope: CoroutineScope) {
|
|||||||
fun <T> SharedFlow<T>.launch(onEach: suspend (T) -> Unit) {
|
fun <T> SharedFlow<T>.launch(onEach: suspend (T) -> Unit) {
|
||||||
this.onEach(onEach).launchIn(scope)
|
this.onEach(onEach).launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> Flow<T>.launch(onEach: suspend (T) -> Unit) {
|
||||||
|
this.onEach(onEach).launchIn(scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EffectScope {
|
interface EffectScope {
|
||||||
|
@ -16,6 +16,5 @@ class ApplicationPreferences(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmInline
|
data class ApplicationVersion(val value: Int)
|
||||||
value class ApplicationVersion(val value: Int)
|
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package app.dapk.st.directory
|
package app.dapk.st.directory
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.dapk.st.core.ProvidableModule
|
|
||||||
import app.dapk.st.state.createStateViewModel
|
|
||||||
import app.dapk.st.core.JobBag
|
import app.dapk.st.core.JobBag
|
||||||
|
import app.dapk.st.core.ProvidableModule
|
||||||
|
import app.dapk.st.directory.state.DirectoryEvent
|
||||||
import app.dapk.st.directory.state.DirectoryState
|
import app.dapk.st.directory.state.DirectoryState
|
||||||
import app.dapk.st.directory.state.directoryReducer
|
import app.dapk.st.directory.state.directoryReducer
|
||||||
import app.dapk.st.engine.ChatEngine
|
import app.dapk.st.engine.ChatEngine
|
||||||
|
import app.dapk.st.state.createStateViewModel
|
||||||
|
|
||||||
class DirectoryModule(
|
class DirectoryModule(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@ -14,6 +15,8 @@ class DirectoryModule(
|
|||||||
) : ProvidableModule {
|
) : ProvidableModule {
|
||||||
|
|
||||||
fun directoryState(): DirectoryState {
|
fun directoryState(): DirectoryState {
|
||||||
return createStateViewModel { directoryReducer(chatEngine, ShortcutHandler(context), JobBag(), it) }
|
return createStateViewModel { directoryReducer(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun directoryReducer(eventEmitter: suspend (DirectoryEvent) -> Unit) = directoryReducer(chatEngine, ShortcutHandler(context), JobBag(), eventEmitter)
|
||||||
}
|
}
|
||||||
|
@ -8,15 +8,21 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "chat-engine:chat-engine"
|
implementation "chat-engine:chat-engine"
|
||||||
|
implementation 'screen-state:screen-android'
|
||||||
implementation project(":features:directory")
|
implementation project(":features:directory")
|
||||||
implementation project(":features:login")
|
implementation project(":features:login")
|
||||||
implementation project(":features:settings")
|
implementation project(":features:settings")
|
||||||
implementation project(":features:profile")
|
implementation project(":features:profile")
|
||||||
implementation project(":domains:android:compose-core")
|
implementation project(":domains:android:compose-core")
|
||||||
implementation project(":domains:android:viewmodel")
|
|
||||||
implementation project(':domains:store')
|
implementation project(':domains:store')
|
||||||
implementation 'screen-state:screen-android'
|
|
||||||
implementation project(":core")
|
implementation project(":core")
|
||||||
implementation project(":design-library")
|
implementation project(":design-library")
|
||||||
implementation libs.compose.coil
|
implementation libs.compose.coil
|
||||||
|
|
||||||
|
kotlinTest(it)
|
||||||
|
|
||||||
|
testImplementation 'screen-state:state-test'
|
||||||
|
testImplementation 'chat-engine:chat-engine-test'
|
||||||
|
androidImportFixturesWorkaround(project, project(":core"))
|
||||||
|
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
||||||
}
|
}
|
@ -20,7 +20,8 @@ class BetaVersionUpgradeUseCase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun hasChangedVersion(): Boolean {
|
private suspend fun hasChangedVersion(): Boolean {
|
||||||
val previousVersion = applicationPreferences.readVersion()?.value
|
val readVersion = applicationPreferences.readVersion()
|
||||||
|
val previousVersion = readVersion?.value
|
||||||
val currentVersion = buildMeta.versionCode
|
val currentVersion = buildMeta.versionCode
|
||||||
return when (previousVersion) {
|
return when (previousVersion) {
|
||||||
null -> false
|
null -> false
|
||||||
|
@ -1,27 +1,51 @@
|
|||||||
package app.dapk.st.home
|
package app.dapk.st.home
|
||||||
|
|
||||||
|
import app.dapk.st.core.JobBag
|
||||||
import app.dapk.st.core.ProvidableModule
|
import app.dapk.st.core.ProvidableModule
|
||||||
import app.dapk.st.directory.state.DirectoryState
|
import app.dapk.st.directory.DirectoryModule
|
||||||
import app.dapk.st.domain.StoreModule
|
import app.dapk.st.domain.StoreModule
|
||||||
import app.dapk.st.engine.ChatEngine
|
import app.dapk.st.engine.ChatEngine
|
||||||
import app.dapk.st.login.state.LoginState
|
import app.dapk.st.home.state.homeReducer
|
||||||
import app.dapk.st.profile.state.ProfileState
|
import app.dapk.st.login.LoginModule
|
||||||
|
import app.dapk.st.profile.ProfileModule
|
||||||
|
import app.dapk.st.state.State
|
||||||
|
import app.dapk.st.state.createStateViewModel
|
||||||
|
import app.dapk.state.Action
|
||||||
|
import app.dapk.state.DynamicReducers
|
||||||
|
import app.dapk.state.combineReducers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
|
|
||||||
class HomeModule(
|
class HomeModule(
|
||||||
private val chatEngine: ChatEngine,
|
private val chatEngine: ChatEngine,
|
||||||
private val storeModule: StoreModule,
|
private val storeModule: StoreModule,
|
||||||
val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase,
|
val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase,
|
||||||
|
private val profileModule: ProfileModule,
|
||||||
|
private val loginModule: LoginModule,
|
||||||
|
private val directoryModule: DirectoryModule,
|
||||||
) : ProvidableModule {
|
) : ProvidableModule {
|
||||||
|
|
||||||
internal fun homeViewModel(directory: DirectoryState, login: LoginState, profile: ProfileState): HomeViewModel {
|
internal fun compositeHomeState(): DynamicState {
|
||||||
return HomeViewModel(
|
return createStateViewModel {
|
||||||
chatEngine,
|
combineReducers(
|
||||||
directory,
|
listOf(
|
||||||
login,
|
homeReducerFactory(it),
|
||||||
profile,
|
loginModule.loginReducer(it),
|
||||||
storeModule.cacheCleaner(),
|
profileModule.profileReducer(),
|
||||||
betaVersionUpgradeUseCase,
|
directoryModule.directoryReducer(it)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private fun homeReducerFactory(eventEmitter: suspend (Any) -> Unit) =
|
||||||
|
homeReducer(chatEngine, storeModule.cacheCleaner(), betaVersionUpgradeUseCase, JobBag(), eventEmitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias DynamicState = State<DynamicReducers, Any>
|
||||||
|
|
||||||
|
inline fun <reified S, reified E> DynamicState.childState() = object : State<S, E> {
|
||||||
|
override fun dispatch(action: Action) = this@childState.dispatch(action)
|
||||||
|
override val events: Flow<E> = this@childState.events.filterIsInstance()
|
||||||
|
override val current: S = this@childState.current.getState()
|
||||||
|
}
|
||||||
|
@ -10,34 +10,39 @@ import app.dapk.st.core.LifecycleEffect
|
|||||||
import app.dapk.st.core.components.CenteredLoading
|
import app.dapk.st.core.components.CenteredLoading
|
||||||
import app.dapk.st.design.components.CircleishAvatar
|
import app.dapk.st.design.components.CircleishAvatar
|
||||||
import app.dapk.st.directory.DirectoryScreen
|
import app.dapk.st.directory.DirectoryScreen
|
||||||
import app.dapk.st.home.HomeScreenState.*
|
import app.dapk.st.directory.state.DirectoryState
|
||||||
import app.dapk.st.home.HomeScreenState.Page.Directory
|
import app.dapk.st.home.state.HomeAction
|
||||||
import app.dapk.st.home.HomeScreenState.Page.Profile
|
import app.dapk.st.home.state.HomeScreenState.*
|
||||||
|
import app.dapk.st.home.state.HomeScreenState.Page.Directory
|
||||||
|
import app.dapk.st.home.state.HomeScreenState.Page.Profile
|
||||||
|
import app.dapk.st.home.state.HomeState
|
||||||
import app.dapk.st.login.LoginScreen
|
import app.dapk.st.login.LoginScreen
|
||||||
|
import app.dapk.st.login.state.LoginState
|
||||||
import app.dapk.st.profile.ProfileScreen
|
import app.dapk.st.profile.ProfileScreen
|
||||||
|
import app.dapk.st.profile.state.ProfileState
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun HomeScreen(homeViewModel: HomeViewModel) {
|
internal fun HomeScreen(homeState: HomeState, directoryState: DirectoryState, loginState: LoginState, profileState: ProfileState) {
|
||||||
LifecycleEffect(
|
LifecycleEffect(
|
||||||
onStart = { homeViewModel.start() },
|
onStart = { homeState.dispatch(HomeAction.LifecycleVisible) },
|
||||||
onStop = { homeViewModel.stop() }
|
onStop = { homeState.dispatch(HomeAction.LifecycleGone) }
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val state = homeViewModel.state) {
|
when (val state = homeState.current) {
|
||||||
Loading -> CenteredLoading()
|
Loading -> CenteredLoading()
|
||||||
is SignedIn -> {
|
is SignedIn -> {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
BottomBar(state, homeViewModel)
|
BottomBar(state, homeState)
|
||||||
},
|
},
|
||||||
content = { innerPadding ->
|
content = { innerPadding ->
|
||||||
Box(modifier = Modifier.padding(innerPadding)) {
|
Box(modifier = Modifier.padding(innerPadding)) {
|
||||||
when (state.page) {
|
when (state.page) {
|
||||||
Directory -> DirectoryScreen(homeViewModel.directory())
|
Directory -> DirectoryScreen(directoryState)
|
||||||
Profile -> {
|
Profile -> {
|
||||||
ProfileScreen(homeViewModel.profile()) {
|
ProfileScreen(profileState) {
|
||||||
homeViewModel.changePage(Directory)
|
homeState.dispatch(HomeAction.ChangePage(Directory))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,8 +52,8 @@ internal fun HomeScreen(homeViewModel: HomeViewModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SignedOut -> {
|
SignedOut -> {
|
||||||
LoginScreen(homeViewModel.login()) {
|
LoginScreen(loginState) {
|
||||||
homeViewModel.loggedIn()
|
homeState.dispatch(HomeAction.LoggedIn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,7 +61,7 @@ internal fun HomeScreen(homeViewModel: HomeViewModel) {
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomBar(state: SignedIn, homeViewModel: HomeViewModel) {
|
private fun BottomBar(state: SignedIn, homeState: HomeState) {
|
||||||
Column {
|
Column {
|
||||||
Divider(modifier = Modifier.fillMaxWidth(), color = Color.Black.copy(alpha = 0.2f), thickness = 0.5.dp)
|
Divider(modifier = Modifier.fillMaxWidth(), color = Color.Black.copy(alpha = 0.2f), thickness = 0.5.dp)
|
||||||
NavigationBar(containerColor = Color.Transparent, modifier = Modifier.height(IntrinsicSize.Min)) {
|
NavigationBar(containerColor = Color.Transparent, modifier = Modifier.height(IntrinsicSize.Min)) {
|
||||||
@ -67,8 +72,8 @@ private fun BottomBar(state: SignedIn, homeViewModel: HomeViewModel) {
|
|||||||
selected = state.page == page,
|
selected = state.page == page,
|
||||||
onClick = {
|
onClick = {
|
||||||
when {
|
when {
|
||||||
state.page == page -> homeViewModel.scrollToTopOfMessages()
|
state.page == page -> homeState.dispatch(HomeAction.ScrollToTop)
|
||||||
else -> homeViewModel.changePage(page)
|
else -> homeState.dispatch(HomeAction.ChangePage(page))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -86,7 +91,7 @@ private fun BottomBar(state: SignedIn, homeViewModel: HomeViewModel) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
selected = state.page == page,
|
selected = state.page == page,
|
||||||
onClick = { homeViewModel.changePage(page) },
|
onClick = { homeState.dispatch(HomeAction.ChangePage(page)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
package app.dapk.st.home
|
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import app.dapk.st.directory.state.ComponentLifecycle
|
|
||||||
import app.dapk.st.directory.state.DirectorySideEffect
|
|
||||||
import app.dapk.st.directory.state.DirectoryState
|
|
||||||
import app.dapk.st.domain.StoreCleaner
|
|
||||||
import app.dapk.st.engine.ChatEngine
|
|
||||||
import app.dapk.st.home.HomeScreenState.*
|
|
||||||
import app.dapk.st.login.state.LoginState
|
|
||||||
import app.dapk.st.profile.state.ProfileAction
|
|
||||||
import app.dapk.st.profile.state.ProfileState
|
|
||||||
import app.dapk.st.viewmodel.DapkViewModel
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
internal class HomeViewModel(
|
|
||||||
private val chatEngine: ChatEngine,
|
|
||||||
private val directoryState: DirectoryState,
|
|
||||||
private val loginState: LoginState,
|
|
||||||
private val profileState: ProfileState,
|
|
||||||
private val cacheCleaner: StoreCleaner,
|
|
||||||
private val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase,
|
|
||||||
) : DapkViewModel<HomeScreenState, HomeEvent>(
|
|
||||||
initialState = Loading
|
|
||||||
) {
|
|
||||||
|
|
||||||
private var listenForInvitesJob: Job? = null
|
|
||||||
|
|
||||||
fun directory() = directoryState
|
|
||||||
fun login() = loginState
|
|
||||||
fun profile() = profileState
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
state = if (chatEngine.isSignedIn()) {
|
|
||||||
_events.emit(HomeEvent.OnShowContent)
|
|
||||||
initialHomeContent()
|
|
||||||
} else {
|
|
||||||
SignedOut
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
|
||||||
if (chatEngine.isSignedIn()) {
|
|
||||||
listenForInviteChanges()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun initialHomeContent(): SignedIn {
|
|
||||||
val me = chatEngine.me(forceRefresh = false)
|
|
||||||
return when (val current = state) {
|
|
||||||
Loading -> SignedIn(Page.Directory, me, invites = 0)
|
|
||||||
is SignedIn -> current.copy(me = me, invites = current.invites)
|
|
||||||
SignedOut -> SignedIn(Page.Directory, me, invites = 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loggedIn() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
state = initialHomeContent()
|
|
||||||
_events.emit(HomeEvent.OnShowContent)
|
|
||||||
listenForInviteChanges()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CoroutineScope.listenForInviteChanges() {
|
|
||||||
listenForInvitesJob?.cancel()
|
|
||||||
listenForInvitesJob = chatEngine.invites()
|
|
||||||
.onEach { invites ->
|
|
||||||
when (val currentState = state) {
|
|
||||||
is SignedIn -> updateState { currentState.copy(invites = invites.size) }
|
|
||||||
Loading,
|
|
||||||
SignedOut -> {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.launchIn(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasVersionChanged() = betaVersionUpgradeUseCase.hasVersionChanged()
|
|
||||||
|
|
||||||
fun clearCache() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
cacheCleaner.cleanCache(removeCredentials = false)
|
|
||||||
betaVersionUpgradeUseCase.notifyUpgraded()
|
|
||||||
_events.emit(HomeEvent.Relaunch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun scrollToTopOfMessages() {
|
|
||||||
directoryState.dispatch(DirectorySideEffect.ScrollToTop)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun changePage(page: Page) {
|
|
||||||
state = when (val current = state) {
|
|
||||||
Loading -> current
|
|
||||||
is SignedIn -> {
|
|
||||||
when (page) {
|
|
||||||
current.page -> current
|
|
||||||
else -> current.copy(page = page).also {
|
|
||||||
pageChangeSideEffects(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SignedOut -> current
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pageChangeSideEffects(page: Page) {
|
|
||||||
when (page) {
|
|
||||||
Page.Directory -> {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
Page.Profile -> {
|
|
||||||
directoryState.dispatch(ComponentLifecycle.OnGone)
|
|
||||||
profileState.dispatch(ProfileAction.Reset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,26 +12,24 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import app.dapk.st.core.DapkActivity
|
import app.dapk.st.core.DapkActivity
|
||||||
|
import app.dapk.st.core.extensions.unsafeLazy
|
||||||
import app.dapk.st.core.module
|
import app.dapk.st.core.module
|
||||||
import app.dapk.st.core.viewModel
|
import app.dapk.st.home.state.HomeAction
|
||||||
import app.dapk.st.directory.DirectoryModule
|
import app.dapk.st.home.state.HomeEvent
|
||||||
import app.dapk.st.login.LoginModule
|
import app.dapk.st.home.state.HomeState
|
||||||
import app.dapk.st.profile.ProfileModule
|
|
||||||
import app.dapk.st.state.state
|
import app.dapk.st.state.state
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
class MainActivity : DapkActivity() {
|
class MainActivity : DapkActivity() {
|
||||||
|
|
||||||
private val directoryState by state { module<DirectoryModule>().directoryState() }
|
private val homeModule by unsafeLazy { module<HomeModule>() }
|
||||||
private val loginState by state { module<LoginModule>().loginState() }
|
private val compositeState by state { homeModule.compositeHomeState() }
|
||||||
private val profileState by state { module<ProfileModule>().profileState() }
|
|
||||||
private val homeViewModel by viewModel { module<HomeModule>().homeViewModel(directoryState, loginState, profileState) }
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val pushPermissionLauncher = registerPushPermission()
|
val pushPermissionLauncher = registerPushPermission()
|
||||||
homeViewModel.events.onEach {
|
compositeState.events.onEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
HomeEvent.Relaunch -> recreate()
|
HomeEvent.Relaunch -> recreate()
|
||||||
HomeEvent.OnShowContent -> pushPermissionLauncher?.invoke()
|
HomeEvent.OnShowContent -> pushPermissionLauncher?.invoke()
|
||||||
@ -39,11 +37,12 @@ class MainActivity : DapkActivity() {
|
|||||||
}.launchIn(lifecycleScope)
|
}.launchIn(lifecycleScope)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
if (homeViewModel.hasVersionChanged()) {
|
val homeState: HomeState = compositeState.childState()
|
||||||
BetaUpgradeDialog()
|
if (homeModule.betaVersionUpgradeUseCase.hasVersionChanged()) {
|
||||||
|
BetaUpgradeDialog(homeState)
|
||||||
} else {
|
} else {
|
||||||
Surface(Modifier.fillMaxSize()) {
|
Surface(Modifier.fillMaxSize()) {
|
||||||
HomeScreen(homeViewModel)
|
HomeScreen(homeState, compositeState.childState(), compositeState.childState(), compositeState.childState())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,20 +55,20 @@ class MainActivity : DapkActivity() {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Composable
|
|
||||||
private fun BetaUpgradeDialog() {
|
@Composable
|
||||||
AlertDialog(
|
private fun BetaUpgradeDialog(homeState: HomeState) {
|
||||||
title = { Text(text = "BETA") },
|
AlertDialog(
|
||||||
text = { Text(text = "During the BETA, version upgrades require a cache clear") },
|
title = { Text(text = "BETA") },
|
||||||
onDismissRequest = {
|
text = { Text(text = "During the BETA, version upgrades require a cache clear") },
|
||||||
|
onDismissRequest = {
|
||||||
},
|
|
||||||
confirmButton = {
|
},
|
||||||
TextButton(onClick = { homeViewModel.clearCache() }) {
|
confirmButton = {
|
||||||
Text(text = "Clear cache".uppercase())
|
TextButton(onClick = { homeState.dispatch(HomeAction.ClearCache) }) {
|
||||||
}
|
Text(text = "Clear cache".uppercase())
|
||||||
},
|
}
|
||||||
)
|
},
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package app.dapk.st.home.state
|
||||||
|
|
||||||
|
import app.dapk.st.engine.Me
|
||||||
|
import app.dapk.st.home.state.HomeScreenState.Page
|
||||||
|
import app.dapk.state.Action
|
||||||
|
|
||||||
|
sealed interface HomeAction : Action {
|
||||||
|
object LifecycleVisible : HomeAction
|
||||||
|
object LifecycleGone : HomeAction
|
||||||
|
|
||||||
|
object ScrollToTop : HomeAction
|
||||||
|
object ClearCache : HomeAction
|
||||||
|
object LoggedIn : HomeAction
|
||||||
|
|
||||||
|
data class ChangePage(val page: Page) : HomeAction
|
||||||
|
data class ChangePageSideEffect(val page: Page) : HomeAction
|
||||||
|
data class UpdateInvitesCount(val invitesCount: Int) : HomeAction
|
||||||
|
data class UpdateToSignedIn(val me: Me) : HomeAction
|
||||||
|
data class UpdateState(val state: HomeScreenState) : HomeAction
|
||||||
|
object InitialHome : HomeAction
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
package app.dapk.st.home.state
|
||||||
|
|
||||||
|
import app.dapk.st.core.JobBag
|
||||||
|
import app.dapk.st.directory.state.ComponentLifecycle
|
||||||
|
import app.dapk.st.directory.state.DirectorySideEffect
|
||||||
|
import app.dapk.st.domain.StoreCleaner
|
||||||
|
import app.dapk.st.engine.ChatEngine
|
||||||
|
import app.dapk.st.home.BetaVersionUpgradeUseCase
|
||||||
|
import app.dapk.st.profile.state.ProfileAction
|
||||||
|
import app.dapk.state.*
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
|
fun homeReducer(
|
||||||
|
chatEngine: ChatEngine,
|
||||||
|
cacheCleaner: StoreCleaner,
|
||||||
|
betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase,
|
||||||
|
jobBag: JobBag,
|
||||||
|
eventEmitter: suspend (HomeEvent) -> Unit,
|
||||||
|
): ReducerFactory<HomeScreenState> {
|
||||||
|
return createReducer(
|
||||||
|
initialState = HomeScreenState.Loading,
|
||||||
|
|
||||||
|
change(HomeAction.UpdateState::class) { action, _ ->
|
||||||
|
action.state
|
||||||
|
},
|
||||||
|
|
||||||
|
change(HomeAction.UpdateToSignedIn::class) { action, state ->
|
||||||
|
val me = action.me
|
||||||
|
when (state) {
|
||||||
|
HomeScreenState.Loading -> HomeScreenState.SignedIn(HomeScreenState.Page.Directory, me, invites = 0)
|
||||||
|
is HomeScreenState.SignedIn -> state.copy(me = me, invites = state.invites)
|
||||||
|
HomeScreenState.SignedOut -> HomeScreenState.SignedIn(HomeScreenState.Page.Directory, me, invites = 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
change(HomeAction.UpdateInvitesCount::class) { action, state ->
|
||||||
|
when (state) {
|
||||||
|
HomeScreenState.Loading -> state
|
||||||
|
is HomeScreenState.SignedIn -> state.copy(invites = action.invitesCount)
|
||||||
|
HomeScreenState.SignedOut -> state
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async(HomeAction.LifecycleVisible::class) { _ ->
|
||||||
|
if (chatEngine.isSignedIn()) {
|
||||||
|
eventEmitter.invoke(HomeEvent.OnShowContent)
|
||||||
|
dispatch(HomeAction.InitialHome)
|
||||||
|
} else {
|
||||||
|
dispatch(HomeAction.UpdateState(HomeScreenState.SignedOut))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async(HomeAction.InitialHome::class) {
|
||||||
|
val me = chatEngine.me(forceRefresh = false)
|
||||||
|
dispatch(HomeAction.UpdateToSignedIn(me))
|
||||||
|
listenForInviteChanges(chatEngine, jobBag)
|
||||||
|
},
|
||||||
|
|
||||||
|
async(HomeAction.LoggedIn::class) {
|
||||||
|
dispatch(HomeAction.InitialHome)
|
||||||
|
eventEmitter.invoke(HomeEvent.OnShowContent)
|
||||||
|
},
|
||||||
|
|
||||||
|
async(HomeAction.ChangePageSideEffect::class) { action ->
|
||||||
|
when (action.page) {
|
||||||
|
HomeScreenState.Page.Directory -> {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
HomeScreenState.Page.Profile -> {
|
||||||
|
dispatch(ComponentLifecycle.OnGone)
|
||||||
|
dispatch(ProfileAction.Reset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
multi(HomeAction.ChangePage::class) { action ->
|
||||||
|
change { _, state ->
|
||||||
|
when (state) {
|
||||||
|
is HomeScreenState.SignedIn -> when (action.page) {
|
||||||
|
state.page -> state
|
||||||
|
else -> state.copy(page = action.page)
|
||||||
|
}
|
||||||
|
|
||||||
|
HomeScreenState.Loading -> state
|
||||||
|
HomeScreenState.SignedOut -> state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async {
|
||||||
|
val state = getState()
|
||||||
|
if (state is HomeScreenState.SignedIn && state.page != action.page) {
|
||||||
|
dispatch(HomeAction.ChangePageSideEffect(action.page))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async(HomeAction.ScrollToTop::class) {
|
||||||
|
dispatch(DirectorySideEffect.ScrollToTop)
|
||||||
|
},
|
||||||
|
|
||||||
|
sideEffect(HomeAction.ClearCache::class) { _, _ ->
|
||||||
|
cacheCleaner.cleanCache(removeCredentials = false)
|
||||||
|
betaVersionUpgradeUseCase.notifyUpgraded()
|
||||||
|
eventEmitter.invoke(HomeEvent.Relaunch)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ReducerScope<HomeScreenState>.listenForInviteChanges(chatEngine: ChatEngine, jobBag: JobBag) {
|
||||||
|
jobBag.replace(
|
||||||
|
"invites-count",
|
||||||
|
chatEngine.invites()
|
||||||
|
.onEach { invites ->
|
||||||
|
when (getState()) {
|
||||||
|
is HomeScreenState.SignedIn -> dispatch(HomeAction.UpdateInvitesCount(invites.size))
|
||||||
|
HomeScreenState.Loading,
|
||||||
|
HomeScreenState.SignedOut -> {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launchIn(coroutineScope)
|
||||||
|
)
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package app.dapk.st.home
|
package app.dapk.st.home.state
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import app.dapk.st.engine.Me
|
import app.dapk.st.engine.Me
|
||||||
|
import app.dapk.st.state.State
|
||||||
|
|
||||||
|
typealias HomeState = State<HomeScreenState, HomeEvent>
|
||||||
|
|
||||||
sealed interface HomeScreenState {
|
sealed interface HomeScreenState {
|
||||||
|
|
@ -0,0 +1,68 @@
|
|||||||
|
package app.dapk.st.home
|
||||||
|
|
||||||
|
import app.dapk.st.core.BuildMeta
|
||||||
|
import app.dapk.st.domain.ApplicationPreferences
|
||||||
|
import app.dapk.st.domain.ApplicationVersion
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import test.delegateReturn
|
||||||
|
import test.expect
|
||||||
|
|
||||||
|
class BetaVersionUpgradeUseCaseTest {
|
||||||
|
|
||||||
|
private val buildMeta = BuildMeta(versionName = "a-version-name", versionCode = 100, isDebug = false)
|
||||||
|
private val fakeApplicationPreferences = FakeApplicationPreferences()
|
||||||
|
|
||||||
|
private val useCase = BetaVersionUpgradeUseCase(
|
||||||
|
fakeApplicationPreferences.instance,
|
||||||
|
buildMeta
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given same stored version, when hasVersionChanged then is false`() = runTest {
|
||||||
|
fakeApplicationPreferences.givenVersion().returns(ApplicationVersion(buildMeta.versionCode))
|
||||||
|
|
||||||
|
val result = useCase.hasVersionChanged()
|
||||||
|
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be impossible
|
||||||
|
@Test
|
||||||
|
fun `given higher stored version, when hasVersionChanged then is false`() = runTest {
|
||||||
|
fakeApplicationPreferences.givenVersion().returns(ApplicationVersion(buildMeta.versionCode + 1))
|
||||||
|
|
||||||
|
val result = useCase.hasVersionChanged()
|
||||||
|
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given lower stored version, when hasVersionChanged then is true`() = runTest {
|
||||||
|
fakeApplicationPreferences.givenVersion().returns(ApplicationVersion(buildMeta.versionCode - 1))
|
||||||
|
|
||||||
|
val result = useCase.hasVersionChanged()
|
||||||
|
|
||||||
|
result shouldBeEqualTo true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given version has changed, when waiting, then blocks until notified of upgrade`() = runTest {
|
||||||
|
fakeApplicationPreferences.givenVersion().returns(ApplicationVersion(buildMeta.versionCode - 1))
|
||||||
|
fakeApplicationPreferences.instance.expect { it.setVersion(ApplicationVersion(buildMeta.versionCode)) }
|
||||||
|
|
||||||
|
val waitUntilReady = async { useCase.waitUnitReady() }
|
||||||
|
async { useCase.notifyUpgraded() }
|
||||||
|
waitUntilReady.await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeApplicationPreferences {
|
||||||
|
val instance = mockk<ApplicationPreferences>()
|
||||||
|
|
||||||
|
fun givenVersion() = coEvery { instance.readVersion() }.delegateReturn()
|
||||||
|
}
|
@ -0,0 +1,224 @@
|
|||||||
|
package app.dapk.st.home.state
|
||||||
|
|
||||||
|
import app.dapk.st.directory.state.ComponentLifecycle
|
||||||
|
import app.dapk.st.directory.state.DirectorySideEffect
|
||||||
|
import app.dapk.st.domain.StoreCleaner
|
||||||
|
import app.dapk.st.engine.Me
|
||||||
|
import app.dapk.st.home.BetaVersionUpgradeUseCase
|
||||||
|
import app.dapk.st.matrix.common.HomeServerUrl
|
||||||
|
import app.dapk.st.profile.state.ProfileAction
|
||||||
|
import fake.FakeChatEngine
|
||||||
|
import fake.FakeJobBag
|
||||||
|
import fixture.aRoomId
|
||||||
|
import fixture.aRoomInvite
|
||||||
|
import fixture.aUserId
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.Test
|
||||||
|
import test.*
|
||||||
|
|
||||||
|
private val A_ME = Me(aUserId(), displayName = null, avatarUrl = null, homeServerUrl = HomeServerUrl("ignored"))
|
||||||
|
private val A_SIGNED_IN_STATE = HomeScreenState.SignedIn(
|
||||||
|
HomeScreenState.Page.Directory,
|
||||||
|
me = A_ME,
|
||||||
|
invites = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
class HomeReducerTest {
|
||||||
|
|
||||||
|
private val fakeStoreCleaner = FakeStoreCleaner()
|
||||||
|
private val fakeChatEngine = FakeChatEngine()
|
||||||
|
private val fakeBetaVersionUpgradeUseCase = FakeBetaVersionUpgradeUseCase()
|
||||||
|
private val fakeJobBag = FakeJobBag()
|
||||||
|
|
||||||
|
private val runReducerTest = testReducer { fakeEventSource ->
|
||||||
|
homeReducer(
|
||||||
|
fakeChatEngine,
|
||||||
|
fakeStoreCleaner,
|
||||||
|
fakeBetaVersionUpgradeUseCase.instance,
|
||||||
|
fakeJobBag.instance,
|
||||||
|
fakeEventSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state is loading`() = runReducerTest {
|
||||||
|
assertInitialState(HomeScreenState.Loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when UpdateState, then replaces state`() = runReducerTest {
|
||||||
|
reduce(HomeAction.UpdateState(HomeScreenState.SignedOut))
|
||||||
|
|
||||||
|
assertOnlyStateChange(HomeScreenState.SignedOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedIn, when UpdateInviteCount, then updates invite count`() = runReducerTest {
|
||||||
|
setState(A_SIGNED_IN_STATE)
|
||||||
|
|
||||||
|
reduce(HomeAction.UpdateInvitesCount(invitesCount = 90))
|
||||||
|
|
||||||
|
assertOnlyStateChange(A_SIGNED_IN_STATE.copy(invites = 90))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when ScrollToTop, then forwards to directory scroll event`() = runReducerTest {
|
||||||
|
reduce(HomeAction.ScrollToTop)
|
||||||
|
|
||||||
|
assertOnlyDispatches(DirectorySideEffect.ScrollToTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when ClearCache, then clears store cache, upgrades and relaunches`() = runReducerTest {
|
||||||
|
fakeStoreCleaner.expect { it.cleanCache(removeCredentials = false) }
|
||||||
|
fakeBetaVersionUpgradeUseCase.instance.expect { it.notifyUpgraded() }
|
||||||
|
|
||||||
|
reduce(HomeAction.ClearCache)
|
||||||
|
|
||||||
|
assertOnlyEvents(HomeEvent.Relaunch)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedIn and invites update, when Visible, then show content and update on invite changes`() = runReducerTest {
|
||||||
|
fakeChatEngine.givenIsSignedIn().returns(true)
|
||||||
|
|
||||||
|
reduce(HomeAction.LifecycleVisible)
|
||||||
|
|
||||||
|
assertEvents(HomeEvent.OnShowContent)
|
||||||
|
assertDispatches(HomeAction.InitialHome)
|
||||||
|
assertNoStateChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedOut and invites update, when Visible, then show content and update on invite changes`() = runReducerTest {
|
||||||
|
fakeChatEngine.givenIsSignedIn().returns(false)
|
||||||
|
|
||||||
|
reduce(HomeAction.LifecycleVisible)
|
||||||
|
|
||||||
|
assertOnlyDispatches(HomeAction.UpdateState(HomeScreenState.SignedOut))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedIn, when InitialHome, then updates me state and listens to invite changes`() = runReducerTest {
|
||||||
|
setState(A_SIGNED_IN_STATE)
|
||||||
|
fakeChatEngine.givenMe(forceRefresh = false).returns(A_ME)
|
||||||
|
givenInvites(count = 5)
|
||||||
|
|
||||||
|
reduce(HomeAction.InitialHome)
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
HomeAction.UpdateToSignedIn(A_ME),
|
||||||
|
HomeAction.UpdateInvitesCount(5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedIn, when UpdateToSignedIn, then updates me state`() = runReducerTest {
|
||||||
|
setState(A_SIGNED_IN_STATE)
|
||||||
|
val expectedMe = A_ME.copy(aUserId("another-user"))
|
||||||
|
|
||||||
|
reduce(HomeAction.UpdateToSignedIn(expectedMe))
|
||||||
|
|
||||||
|
assertOnlyStateChange(A_SIGNED_IN_STATE.copy(me = expectedMe))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given Loading, when UpdateToSignedIn, then set SignedIn and updates me state`() = runReducerTest {
|
||||||
|
setState(HomeScreenState.Loading)
|
||||||
|
val expectedMe = A_ME.copy(aUserId("another-user"))
|
||||||
|
|
||||||
|
reduce(HomeAction.UpdateToSignedIn(expectedMe))
|
||||||
|
|
||||||
|
assertOnlyStateChange(A_SIGNED_IN_STATE.copy(me = expectedMe))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedOut, when UpdateToSignedIn, then set SignedIn and updates me state`() = runReducerTest {
|
||||||
|
setState(HomeScreenState.SignedOut)
|
||||||
|
val expectedMe = A_ME.copy(aUserId("another-user"))
|
||||||
|
|
||||||
|
reduce(HomeAction.UpdateToSignedIn(expectedMe))
|
||||||
|
|
||||||
|
assertOnlyStateChange(A_SIGNED_IN_STATE.copy(me = expectedMe))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when LoggedIn, then emit show content and fetch initial home`() = runReducerTest {
|
||||||
|
setState(HomeScreenState.SignedOut)
|
||||||
|
givenInvites(count = 0)
|
||||||
|
|
||||||
|
reduce(HomeAction.LoggedIn)
|
||||||
|
|
||||||
|
assertDispatches(HomeAction.InitialHome)
|
||||||
|
assertEvents(HomeEvent.OnShowContent)
|
||||||
|
assertNoStateChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedOut, when ChangePage, then does nothing`() = runReducerTest {
|
||||||
|
setState(HomeScreenState.SignedOut)
|
||||||
|
|
||||||
|
reduce(HomeAction.ChangePage(HomeScreenState.Page.Directory))
|
||||||
|
|
||||||
|
assertNoChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given Loading, when ChangePage, then does nothing`() = runReducerTest {
|
||||||
|
setState(HomeScreenState.Loading)
|
||||||
|
|
||||||
|
reduce(HomeAction.ChangePage(HomeScreenState.Page.Directory))
|
||||||
|
|
||||||
|
assertNoChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedIn, when ChangePage to same page, then does nothing`() = runReducerTest {
|
||||||
|
val page = HomeScreenState.Page.Directory
|
||||||
|
setState(A_SIGNED_IN_STATE.copy(page = page))
|
||||||
|
|
||||||
|
reduce(HomeAction.ChangePage(page))
|
||||||
|
|
||||||
|
assertNoChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given SignedIn, when ChangePage to different page, then updates page and emits side effect`() = runReducerTest {
|
||||||
|
val expectedPage = HomeScreenState.Page.Profile
|
||||||
|
setState(A_SIGNED_IN_STATE.copy(page = HomeScreenState.Page.Directory))
|
||||||
|
|
||||||
|
reduce(HomeAction.ChangePage(expectedPage))
|
||||||
|
|
||||||
|
assertStateChange(A_SIGNED_IN_STATE.copy(page = expectedPage))
|
||||||
|
assertDispatches(HomeAction.ChangePageSideEffect(expectedPage))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when ChangePageSide is Directory, then does nothing`() = runReducerTest {
|
||||||
|
reduce(HomeAction.ChangePageSideEffect(HomeScreenState.Page.Directory))
|
||||||
|
|
||||||
|
assertNoChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when ChangePageSide is Profile, then mark directory gone and resets profile`() = runReducerTest {
|
||||||
|
reduce(HomeAction.ChangePageSideEffect(HomeScreenState.Page.Profile))
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
ComponentLifecycle.OnGone,
|
||||||
|
ProfileAction.Reset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenInvites(count: Int) {
|
||||||
|
fakeJobBag.instance.expect { it.replace("invites-count", any()) }
|
||||||
|
val invites = List(count) { aRoomInvite(roomId = aRoomId(it.toString())) }
|
||||||
|
fakeChatEngine.givenInvites().emits(invites)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeStoreCleaner : StoreCleaner by mockk()
|
||||||
|
|
||||||
|
class FakeBetaVersionUpgradeUseCase {
|
||||||
|
val instance = mockk<BetaVersionUpgradeUseCase>()
|
||||||
|
}
|
@ -3,11 +3,10 @@ package app.dapk.st.login
|
|||||||
import app.dapk.st.core.ProvidableModule
|
import app.dapk.st.core.ProvidableModule
|
||||||
import app.dapk.st.core.extensions.ErrorTracker
|
import app.dapk.st.core.extensions.ErrorTracker
|
||||||
import app.dapk.st.engine.ChatEngine
|
import app.dapk.st.engine.ChatEngine
|
||||||
import app.dapk.st.login.state.LoginState
|
import app.dapk.st.login.state.*
|
||||||
import app.dapk.st.login.state.LoginUseCase
|
|
||||||
import app.dapk.st.login.state.loginReducer
|
|
||||||
import app.dapk.st.push.PushModule
|
import app.dapk.st.push.PushModule
|
||||||
import app.dapk.st.state.createStateViewModel
|
import app.dapk.st.state.createStateViewModel
|
||||||
|
import app.dapk.state.ReducerFactory
|
||||||
|
|
||||||
class LoginModule(
|
class LoginModule(
|
||||||
private val chatEngine: ChatEngine,
|
private val chatEngine: ChatEngine,
|
||||||
@ -17,8 +16,12 @@ class LoginModule(
|
|||||||
|
|
||||||
fun loginState(): LoginState {
|
fun loginState(): LoginState {
|
||||||
return createStateViewModel {
|
return createStateViewModel {
|
||||||
val loginUseCase = LoginUseCase(chatEngine, pushModule.pushTokenRegistrars(), errorTracker)
|
loginReducer(it)
|
||||||
loginReducer(loginUseCase, it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loginReducer(eventEmitter: suspend (LoginEvent) -> Unit): ReducerFactory<LoginScreenState> {
|
||||||
|
val loginUseCase = LoginUseCase(chatEngine, pushModule.pushTokenRegistrars(), errorTracker)
|
||||||
|
return loginReducer(loginUseCase, eventEmitter)
|
||||||
|
}
|
||||||
}
|
}
|
@ -15,7 +15,9 @@ class ProfileModule(
|
|||||||
) : ProvidableModule {
|
) : ProvidableModule {
|
||||||
|
|
||||||
fun profileState(): ProfileState {
|
fun profileState(): ProfileState {
|
||||||
return createStateViewModel { profileReducer(chatEngine, errorTracker, ProfileUseCase(chatEngine, errorTracker), JobBag()) }
|
return createStateViewModel { profileReducer() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun profileReducer() = profileReducer(chatEngine, errorTracker, ProfileUseCase(chatEngine, errorTracker), JobBag())
|
||||||
|
|
||||||
}
|
}
|
@ -144,7 +144,6 @@ internal fun settingsReducer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ignored -> {
|
Ignored -> {
|
||||||
nothing()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToggleDynamicTheme -> async {
|
ToggleDynamicTheme -> async {
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit ea31ab26de443ed5e6bb67ce594e3ce8d5f04ff3
|
Subproject commit d596949ac2b923b02da55ddd78e2e26dc46af82a
|
Loading…
x
Reference in New Issue
Block a user