Nav: First iteration integrating Appyx

This commit is contained in:
ganfra 2022-12-21 17:56:01 +01:00
parent 4c88d8e3c2
commit 2de26a30d5
28 changed files with 566 additions and 280 deletions

View file

@ -2,6 +2,7 @@ plugins {
id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil)
id("kotlin-parcelize")
}
android {
@ -21,6 +22,7 @@ dependencies {
implementation(project(":libraries:matrix"))
implementation(project(":libraries:designsystem"))
implementation(project(":libraries:elementresources"))
implementation(libs.appyx.core)
implementation(libs.mavericks.compose)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit)

View file

@ -49,14 +49,14 @@ import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.designsystem.ElementXTheme
import io.element.android.x.features.login.error.loginError
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.core.SessionId
import timber.log.Timber
@Composable
fun LoginScreen(
viewModel: LoginViewModel = mavericksViewModel(),
onChangeServer: () -> Unit = { },
onLoginWithSuccess: (MatrixClient) -> Unit = { },
onLoginWithSuccess: (SessionId) -> Unit = { },
) {
val state: LoginViewState by viewModel.collectAsState()
val formState: LoginFormState by viewModel.formState
@ -85,7 +85,7 @@ fun LoginContent(
onLoginChanged: (String) -> Unit = {},
onPasswordChanged: (String) -> Unit = {},
onSubmitClicked: () -> Unit = {},
onLoginWithSuccess: (MatrixClient) -> Unit = {},
onLoginWithSuccess: (SessionId) -> Unit = {},
) {
Surface(
modifier = modifier,
@ -105,7 +105,7 @@ fun LoginContent(
)
.padding(horizontal = 16.dp),
) {
val isError = state.loggedInClient is Fail
val isError = state.loggedInSessionId is Fail
// Title
Text(
text = "Welcome back",
@ -160,7 +160,7 @@ fun LoginContent(
),
)
var passwordVisible by remember { mutableStateOf(false) }
if (state.loggedInClient is Loading) {
if (state.loggedInSessionId is Loading) {
// Ensure password is hidden when user submits the form
passwordVisible = false
}
@ -193,9 +193,9 @@ fun LoginContent(
onDone = { onSubmitClicked() }
),
)
if (state.loggedInClient is Fail) {
if (state.loggedInSessionId is Fail) {
Text(
text = loginError(state.formState, state.loggedInClient.error),
text = loginError(state.formState, state.loggedInSessionId.error),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp)
@ -212,12 +212,12 @@ fun LoginContent(
) {
Text(text = "Continue")
}
when (val loggedInClient = state.loggedInClient) {
is Success -> onLoginWithSuccess(loggedInClient())
when (val loggedInSessionId = state.loggedInSessionId) {
is Success -> onLoginWithSuccess(loggedInSessionId())
else -> Unit
}
}
if (state.loggedInClient is Loading) {
if (state.loggedInSessionId is Loading) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)

View file

@ -48,22 +48,20 @@ class LoginViewModel @AssistedInject constructor(
val state = awaitState()
// Ensure the server is provided to the Rust SDK
matrix.setHomeserver(state.homeserver)
matrix.login(state.formState.login.trim(), state.formState.password.trim()).also {
it.startSync()
}
matrix.login(state.formState.login.trim(), state.formState.password.trim())
}.execute {
copy(loggedInClient = it)
copy(loggedInSessionId = it)
}
}
}
fun onSetPassword(password: String) {
formState.value = formState.value.copy(password = password)
setState { copy(loggedInClient = Uninitialized) }
setState { copy(loggedInSessionId = Uninitialized) }
}
fun onSetName(name: String) {
formState.value = formState.value.copy(login = name)
setState { copy(loggedInClient = Uninitialized) }
setState { copy(loggedInSessionId = Uninitialized) }
}
}

View file

@ -4,15 +4,15 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.core.SessionId
data class LoginViewState(
val homeserver: String = "",
val loggedInClient: Async<MatrixClient> = Uninitialized,
val loggedInSessionId: Async<SessionId> = Uninitialized,
val formState: LoginFormState = LoginFormState.Default,
) : MavericksState {
val submitEnabled =
formState.login.isNotEmpty() && formState.password.isNotEmpty() && loggedInClient !is Loading
formState.login.isNotEmpty() && formState.password.isNotEmpty() && loggedInSessionId !is Loading
}
data class LoginFormState(

View file

@ -0,0 +1,57 @@
package io.element.android.x.features.login.node
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.pop
import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.x.core.di.viewModelSupportNode
import io.element.android.x.features.login.LoginScreen
import io.element.android.x.features.login.changeserver.ChangeServerScreen
import kotlinx.parcelize.Parcelize
class LoginFlowNode(
buildContext: BuildContext,
private val backstack: BackStack<NavTarget> = BackStack(
initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap,
),
) : ParentNode<LoginFlowNode.NavTarget>(
navModel = backstack,
buildContext = buildContext
) {
sealed interface NavTarget : Parcelable {
@Parcelize
object Root : NavTarget
@Parcelize
object ChangeServer : NavTarget
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.Root -> viewModelSupportNode(buildContext) {
LoginScreen(
onChangeServer = { backstack.push(NavTarget.ChangeServer) }
)
}
NavTarget.ChangeServer -> viewModelSupportNode(buildContext) {
ChangeServerScreen(
onChangeServerSuccess = { backstack.pop() }
)
}
}
}
@Composable
override fun View(modifier: Modifier) {
Children(navModel = backstack)
}
}

View file

@ -21,6 +21,7 @@ dependencies {
implementation(project(":libraries:matrix"))
implementation(project(":libraries:designsystem"))
implementation(project(":libraries:textcomposer"))
implementation(libs.appyx.core)
implementation(libs.mavericks.compose)
implementation(libs.coil.compose)
implementation(libs.datetime)

View file

@ -14,6 +14,7 @@ dependencies {
implementation(libs.mavericks.compose)
implementation(libs.accompanist.pager)
implementation(libs.accompanist.pagerindicator)
implementation(libs.appyx.core)
testImplementation(libs.test.junit)
androidTestImplementation(libs.test.junitext)
ksp(libs.showkase.processor)

View file

@ -11,6 +11,7 @@ class SplashCarouselStateFactory {
fun hero(@DrawableRes lightDrawable: Int, @DrawableRes darkDrawable: Int) =
if (lightTheme) lightDrawable else darkDrawable
return SplashCarouselState(
listOf(
SplashCarouselState.Item(

View file

@ -19,6 +19,7 @@ dependencies {
implementation(project(":libraries:core"))
implementation(project(":libraries:matrix"))
implementation(project(":libraries:designsystem"))
implementation(libs.appyx.core)
implementation(libs.mavericks.compose)
implementation(libs.datetime)
implementation(libs.accompanist.placeholder)