Nav: First iteration integrating Appyx
This commit is contained in:
parent
4c88d8e3c2
commit
2de26a30d5
28 changed files with 566 additions and 280 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class SplashCarouselStateFactory {
|
|||
|
||||
fun hero(@DrawableRes lightDrawable: Int, @DrawableRes darkDrawable: Int) =
|
||||
if (lightTheme) lightDrawable else darkDrawable
|
||||
|
||||
return SplashCarouselState(
|
||||
listOf(
|
||||
SplashCarouselState.Item(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue