Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: update text input logic to v2 TextFieldState, part 2 [WPB-8779] #3031

Merged
merged 6 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
Expand All @@ -58,7 +58,6 @@ import com.wire.android.ui.common.WireDialogButtonProperties
import com.wire.android.ui.common.WireDialogButtonType
import com.wire.android.ui.common.progress.WireCircularProgressIndicator
import com.wire.android.ui.common.scaffold.WireScaffold
import com.wire.android.ui.common.textfield.CodeFieldValue
import com.wire.android.ui.common.textfield.CodeTextField
import com.wire.android.ui.common.textfield.WireTextFieldState
import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar
Expand Down Expand Up @@ -90,29 +89,44 @@ fun CreateAccountCodeScreen(

CodeContent(
state = codeState,
onCodeChange = { onCodeChange(it, ::navigateToSummaryScreen) },
textState = codeTextState,
onResendCodePressed = ::resendCode,
onBackPressed = navigator::navigateBack,
onErrorDismiss = ::clearCodeError,
onRemoveDeviceOpen = {
serverConfig = serverConfig
)

(codeState.result as? CreateAccountCodeViewState.Result.Error.DialogError)?.let {
val (title, message) = it.getResources(type = codeState.type)
WireDialog(
title = title,
text = message,
onDismiss = ::clearCodeError,
optionButton1Properties = WireDialogButtonProperties(
onClick = ::clearCodeError,
text = stringResource(id = R.string.label_ok),
type = WireDialogButtonType.Primary,
)
)
}
LaunchedEffect(codeState.result) {
if (codeState.result is CreateAccountCodeViewState.Result.Success) {
navigateToSummaryScreen()
}
if (codeState.result is CreateAccountCodeViewState.Result.Error.TooManyDevicesError) {
clearCodeError()
clearCodeField()
navigator.navigate(NavigationCommand(RemoveDeviceScreenDestination, BackStackMode.CLEAR_WHOLE))
},
serverConfig = serverConfig
)
}
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun CodeContent(
state: CreateAccountCodeViewState,
onCodeChange: (CodeFieldValue) -> Unit,
textState: TextFieldState,
onResendCodePressed: () -> Unit,
onBackPressed: () -> Unit,
onErrorDismiss: () -> Unit,
onRemoveDeviceOpen: () -> Unit,
serverConfig: ServerConfig.Links
) {
val focusRequester = remember { FocusRequester() }
Expand Down Expand Up @@ -152,10 +166,10 @@ private fun CodeContent(
Spacer(modifier = Modifier.weight(1f))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
CodeTextField(
value = state.code.text,
onValueChange = onCodeChange,
state = when (state.error) {
is CreateAccountCodeViewState.CodeError.TextFieldError.InvalidActivationCodeError ->
codeLength = state.codeLength,
textState = textState,
state = when (state.result) {
is CreateAccountCodeViewState.Result.Error.TextFieldError.InvalidActivationCodeError ->
WireTextFieldState.Error(stringResource(id = R.string.create_account_code_error))

else -> WireTextFieldState.Default
Expand All @@ -180,51 +194,36 @@ private fun CodeContent(
keyboardController?.show()
}
}
if (state.error is CreateAccountCodeViewState.CodeError.DialogError) {
val (title, message) = state.error.getResources(type = state.type)
WireDialog(
title = title,
text = message,
onDismiss = onErrorDismiss,
optionButton1Properties = WireDialogButtonProperties(
onClick = onErrorDismiss,
text = stringResource(id = R.string.label_ok),
type = WireDialogButtonType.Primary,
)
)
} else if (state.error is CreateAccountCodeViewState.CodeError.TooManyDevicesError) {
onRemoveDeviceOpen()
}
}

@Composable
private fun CreateAccountCodeViewState.CodeError.DialogError.getResources(type: CreateAccountFlowType) = when (this) {
CreateAccountCodeViewState.CodeError.DialogError.AccountAlreadyExistsError -> DialogErrorStrings(
private fun CreateAccountCodeViewState.Result.Error.DialogError.getResources(type: CreateAccountFlowType) = when (this) {
CreateAccountCodeViewState.Result.Error.DialogError.AccountAlreadyExistsError -> DialogErrorStrings(
stringResource(id = R.string.create_account_code_error_title),
stringResource(id = R.string.create_account_email_already_in_use_error)
)

CreateAccountCodeViewState.CodeError.DialogError.BlackListedError -> DialogErrorStrings(
CreateAccountCodeViewState.Result.Error.DialogError.BlackListedError -> DialogErrorStrings(
stringResource(id = R.string.create_account_code_error_title),
stringResource(id = R.string.create_account_email_blacklisted_error)
)

CreateAccountCodeViewState.CodeError.DialogError.EmailDomainBlockedError -> DialogErrorStrings(
CreateAccountCodeViewState.Result.Error.DialogError.EmailDomainBlockedError -> DialogErrorStrings(
stringResource(id = R.string.create_account_code_error_title),
stringResource(id = R.string.create_account_email_domain_blocked_error)
)

CreateAccountCodeViewState.CodeError.DialogError.InvalidEmailError -> DialogErrorStrings(
CreateAccountCodeViewState.Result.Error.DialogError.InvalidEmailError -> DialogErrorStrings(
stringResource(id = R.string.create_account_code_error_title),
stringResource(id = R.string.create_account_email_invalid_error)
)

CreateAccountCodeViewState.CodeError.DialogError.TeamMembersLimitError -> DialogErrorStrings(
CreateAccountCodeViewState.Result.Error.DialogError.TeamMembersLimitError -> DialogErrorStrings(
stringResource(id = R.string.create_account_code_error_title),
stringResource(id = R.string.create_account_code_error_team_members_limit_reached)
)

CreateAccountCodeViewState.CodeError.DialogError.CreationRestrictedError -> DialogErrorStrings(
CreateAccountCodeViewState.Result.Error.DialogError.CreationRestrictedError -> DialogErrorStrings(
stringResource(id = R.string.create_account_code_error_title),
stringResource(
id = when (type) {
Expand All @@ -234,15 +233,21 @@ private fun CreateAccountCodeViewState.CodeError.DialogError.getResources(type:
)
)
// TODO: sync with design about the error message
CreateAccountCodeViewState.CodeError.DialogError.UserAlreadyExists ->
CreateAccountCodeViewState.Result.Error.DialogError.UserAlreadyExistsError ->
DialogErrorStrings("User Already LoggedIn", "UserAlreadyLoggedIn")

is CreateAccountCodeViewState.CodeError.DialogError.GenericError ->
is CreateAccountCodeViewState.Result.Error.DialogError.GenericError ->
this.coreFailure.dialogErrorStrings(LocalContext.current.resources)
}

@Composable
@Preview
fun PreviewCreateAccountCodeScreen() {
CodeContent(CreateAccountCodeViewState(CreateAccountFlowType.CreatePersonalAccount), {}, {}, {}, {}, {}, ServerConfig.DEFAULT)
CodeContent(
textState = TextFieldState(),
state = CreateAccountCodeViewState(CreateAccountFlowType.CreatePersonalAccount),
onResendCodePressed = {},
onBackPressed = {},
serverConfig = ServerConfig.DEFAULT
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
*/
package com.wire.android.ui.authentication.create.code

import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.clearText
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
Expand All @@ -30,7 +31,7 @@ import com.wire.android.di.ClientScopeProvider
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.ui.authentication.create.common.CreateAccountFlowType
import com.wire.android.ui.authentication.create.common.CreateAccountNavArgs
import com.wire.android.ui.common.textfield.CodeFieldValue
import com.wire.android.ui.common.textfield.textAsFlow
import com.wire.android.ui.navArgs
import com.wire.android.util.WillNeverOccurError
import com.wire.kalium.logic.CoreLogic
Expand All @@ -44,6 +45,7 @@ import com.wire.kalium.logic.feature.register.RegisterParam
import com.wire.kalium.logic.feature.register.RegisterResult
import com.wire.kalium.logic.feature.register.RequestActivationCodeResult
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -61,11 +63,15 @@ class CreateAccountCodeViewModel @Inject constructor(

val serverConfig: ServerConfig.Links = authServerConfigProvider.authServer.value

val codeTextState: TextFieldState = TextFieldState()
var codeState: CreateAccountCodeViewState by mutableStateOf(CreateAccountCodeViewState(createAccountNavArgs.flowType))

fun onCodeChange(newValue: CodeFieldValue, onSuccess: () -> Unit) {
codeState = codeState.copy(code = newValue, error = CreateAccountCodeViewState.CodeError.None)
if (newValue.isFullyFilled) onCodeContinue(onSuccess)
init {
viewModelScope.launch {
codeTextState.textAsFlow().collectLatest {
if (it.length == codeState.codeLength) onCodeContinue()
}
}
}

fun resendCode() {
Expand All @@ -92,17 +98,17 @@ class CreateAccountCodeViewModel @Inject constructor(
}
}

val codeError = authScope.registerScope.requestActivationCode(createAccountNavArgs.userRegistrationInfo.email).toCodeError()
codeState = codeState.copy(loading = false, error = codeError)
val result = authScope.registerScope.requestActivationCode(createAccountNavArgs.userRegistrationInfo.email).toCodeError()
codeState = codeState.copy(loading = false, result = result)
}
}

fun clearCodeError() {
codeState = codeState.copy(error = CreateAccountCodeViewState.CodeError.None)
codeState = codeState.copy(result = CreateAccountCodeViewState.Result.None)
}

fun clearCodeField() {
codeState = codeState.copy(code = CodeFieldValue(text = TextFieldValue(""), isFullyFilled = false))
codeTextState.clearText()
}

private fun registerParamFromType() = when (createAccountNavArgs.flowType) {
Expand All @@ -112,7 +118,7 @@ class CreateAccountCodeViewModel @Inject constructor(
lastName = createAccountNavArgs.userRegistrationInfo.lastName,
password = createAccountNavArgs.userRegistrationInfo.password,
email = createAccountNavArgs.userRegistrationInfo.email,
emailActivationCode = codeState.code.text.text
emailActivationCode = codeTextState.text.toString()
)

CreateAccountFlowType.CreateTeam ->
Expand All @@ -121,14 +127,14 @@ class CreateAccountCodeViewModel @Inject constructor(
lastName = createAccountNavArgs.userRegistrationInfo.lastName,
password = createAccountNavArgs.userRegistrationInfo.password,
email = createAccountNavArgs.userRegistrationInfo.email,
emailActivationCode = codeState.code.text.text,
emailActivationCode = codeTextState.text.toString(),
teamName = createAccountNavArgs.userRegistrationInfo.teamName,
teamIcon = "default"
)
}

@Suppress("ComplexMethod")
private fun onCodeContinue(onSuccess: () -> Unit) {
private fun onCodeContinue() {
codeState = codeState.copy(loading = true)
viewModelScope.launch {
// create account does not support proxy yet
Expand Down Expand Up @@ -188,24 +194,20 @@ class CreateAccountCodeViewModel @Inject constructor(
}

is RegisterClientResult.Success -> {
onSuccess()
codeState = codeState.copy(result = CreateAccountCodeViewState.Result.Success)
}

is RegisterClientResult.E2EICertificateRequired -> {
// TODO
onSuccess()
codeState = codeState.copy(result = CreateAccountCodeViewState.Result.Success)
}
}
}
}
}

private fun updateCodeErrorState(codeError: CreateAccountCodeViewState.CodeError) {
codeState = if (codeError is CreateAccountCodeViewState.CodeError.None) {
codeState.copy(error = codeError)
} else {
codeState.copy(loading = false, error = codeError)
}
private fun updateCodeErrorState(codeError: CreateAccountCodeViewState.Result.Error) {
codeState = codeState.copy(loading = false, result = codeError)
}

private suspend fun registerClient(userId: UserId, password: String) =
Expand All @@ -218,8 +220,8 @@ class CreateAccountCodeViewModel @Inject constructor(
)

private fun RegisterClientResult.Failure.toCodeError() = when (this) {
is RegisterClientResult.Failure.TooManyClients -> CreateAccountCodeViewState.CodeError.TooManyDevicesError
is RegisterClientResult.Failure.Generic -> CreateAccountCodeViewState.CodeError.DialogError.GenericError(this.genericFailure)
is RegisterClientResult.Failure.TooManyClients -> CreateAccountCodeViewState.Result.Error.TooManyDevicesError
is RegisterClientResult.Failure.Generic -> CreateAccountCodeViewState.Result.Error.DialogError.GenericError(this.genericFailure)
is RegisterClientResult.Failure.InvalidCredentials ->
throw WillNeverOccurError("RegisterClient: wrong password when register client after creating a new account")

Expand All @@ -228,29 +230,30 @@ class CreateAccountCodeViewModel @Inject constructor(
}

private fun RegisterResult.Failure.toCodeError() = when (this) {
RegisterResult.Failure.InvalidActivationCode -> CreateAccountCodeViewState.CodeError.TextFieldError.InvalidActivationCodeError
RegisterResult.Failure.AccountAlreadyExists -> CreateAccountCodeViewState.CodeError.DialogError.AccountAlreadyExistsError
RegisterResult.Failure.BlackListed -> CreateAccountCodeViewState.CodeError.DialogError.BlackListedError
RegisterResult.Failure.EmailDomainBlocked -> CreateAccountCodeViewState.CodeError.DialogError.EmailDomainBlockedError
RegisterResult.Failure.InvalidEmail -> CreateAccountCodeViewState.CodeError.DialogError.InvalidEmailError
RegisterResult.Failure.TeamMembersLimitReached -> CreateAccountCodeViewState.CodeError.DialogError.TeamMembersLimitError
RegisterResult.Failure.UserCreationRestricted -> CreateAccountCodeViewState.CodeError.DialogError.CreationRestrictedError
is RegisterResult.Failure.Generic -> CreateAccountCodeViewState.CodeError.DialogError.GenericError(this.failure)
RegisterResult.Failure.InvalidActivationCode -> CreateAccountCodeViewState.Result.Error.TextFieldError.InvalidActivationCodeError
RegisterResult.Failure.AccountAlreadyExists -> CreateAccountCodeViewState.Result.Error.DialogError.AccountAlreadyExistsError
RegisterResult.Failure.BlackListed -> CreateAccountCodeViewState.Result.Error.DialogError.BlackListedError
RegisterResult.Failure.EmailDomainBlocked -> CreateAccountCodeViewState.Result.Error.DialogError.EmailDomainBlockedError
RegisterResult.Failure.InvalidEmail -> CreateAccountCodeViewState.Result.Error.DialogError.InvalidEmailError
RegisterResult.Failure.TeamMembersLimitReached -> CreateAccountCodeViewState.Result.Error.DialogError.TeamMembersLimitError
RegisterResult.Failure.UserCreationRestricted -> CreateAccountCodeViewState.Result.Error.DialogError.CreationRestrictedError
is RegisterResult.Failure.Generic -> CreateAccountCodeViewState.Result.Error.DialogError.GenericError(this.failure)
}

private fun AddAuthenticatedUserUseCase.Result.Failure.toCodeError() = when (this) {
is AddAuthenticatedUserUseCase.Result.Failure.Generic ->
CreateAccountCodeViewState.CodeError.DialogError.GenericError(this.genericFailure)
CreateAccountCodeViewState.Result.Error.DialogError.GenericError(this.genericFailure)

AddAuthenticatedUserUseCase.Result.Failure.UserAlreadyExists -> CreateAccountCodeViewState.CodeError.DialogError.UserAlreadyExists
AddAuthenticatedUserUseCase.Result.Failure.UserAlreadyExists ->
CreateAccountCodeViewState.Result.Error.DialogError.UserAlreadyExistsError
}

private fun RequestActivationCodeResult.toCodeError() = when (this) {
RequestActivationCodeResult.Failure.AlreadyInUse -> CreateAccountCodeViewState.CodeError.DialogError.AccountAlreadyExistsError
RequestActivationCodeResult.Failure.BlacklistedEmail -> CreateAccountCodeViewState.CodeError.DialogError.BlackListedError
RequestActivationCodeResult.Failure.DomainBlocked -> CreateAccountCodeViewState.CodeError.DialogError.EmailDomainBlockedError
RequestActivationCodeResult.Failure.InvalidEmail -> CreateAccountCodeViewState.CodeError.DialogError.InvalidEmailError
is RequestActivationCodeResult.Failure.Generic -> CreateAccountCodeViewState.CodeError.DialogError.GenericError(this.failure)
RequestActivationCodeResult.Success -> CreateAccountCodeViewState.CodeError.None
RequestActivationCodeResult.Failure.AlreadyInUse -> CreateAccountCodeViewState.Result.Error.DialogError.AccountAlreadyExistsError
RequestActivationCodeResult.Failure.BlacklistedEmail -> CreateAccountCodeViewState.Result.Error.DialogError.BlackListedError
RequestActivationCodeResult.Failure.DomainBlocked -> CreateAccountCodeViewState.Result.Error.DialogError.EmailDomainBlockedError
RequestActivationCodeResult.Failure.InvalidEmail -> CreateAccountCodeViewState.Result.Error.DialogError.InvalidEmailError
is RequestActivationCodeResult.Failure.Generic -> CreateAccountCodeViewState.Result.Error.DialogError.GenericError(this.failure)
RequestActivationCodeResult.Success -> CreateAccountCodeViewState.Result.None
}
}
Loading
Loading