Skip to content

Commit

Permalink
Merge branch 'release/candidate' into fix/app_lock_dialog_blinking
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandreferris authored Nov 24, 2023
2 parents ff0f555 + d0ace3e commit cd716b6
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.window.DialogProperties
import com.wire.android.R
Expand All @@ -51,6 +52,7 @@ import com.wire.android.util.ui.stringWithStyledArgs
@Composable
fun ForgotLockCodeResetDeviceDialog(
username: String,
isPasswordRequired: Boolean,
isPasswordValid: Boolean,
isResetDeviceEnabled: Boolean,
onPasswordChanged: (TextFieldValue) -> Unit,
Expand All @@ -65,14 +67,18 @@ fun ForgotLockCodeResetDeviceDialog(
}
WireDialog(
title = stringResource(R.string.settings_forgot_lock_screen_reset_device),
text = LocalContext.current.resources.stringWithStyledArgs(
R.string.settings_forgot_lock_screen_reset_device_description,
MaterialTheme.wireTypography.body01,
MaterialTheme.wireTypography.body02,
colorsScheme().onBackground,
colorsScheme().onBackground,
username
),
text = if (isPasswordRequired) {
LocalContext.current.resources.stringWithStyledArgs(
R.string.settings_forgot_lock_screen_reset_device_description,
MaterialTheme.wireTypography.body01,
MaterialTheme.wireTypography.body02,
colorsScheme().onBackground,
colorsScheme().onBackground,
username
)
} else {
AnnotatedString(stringResource(id = R.string.settings_forgot_lock_screen_reset_device_without_password_description))
},
onDismiss = onDialogDismissHideKeyboard,
buttonsHorizontalAlignment = false,
dismissButtonProperties = WireDialogButtonProperties(
Expand All @@ -90,23 +96,25 @@ fun ForgotLockCodeResetDeviceDialog(
state = if (!isResetDeviceEnabled) WireButtonState.Disabled else WireButtonState.Error
)
) {
// keyboard controller from outside the Dialog doesn't work inside its content so we have to pass the state
// to the dialog's content and use keyboard controller from there
keyboardController = LocalSoftwareKeyboardController.current
WirePasswordTextField(
state = when {
!isPasswordValid -> WireTextFieldState.Error(stringResource(id = R.string.remove_device_invalid_password))
else -> WireTextFieldState.Default
},
value = backupPassword,
onValueChange = {
backupPassword = it
onPasswordChanged(it)
},
autofill = false,
keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
modifier = Modifier.padding(bottom = dimensions().spacing16x)
)
if (isPasswordRequired) {
// keyboard controller from outside the Dialog doesn't work inside its content so we have to pass the state
// to the dialog's content and use keyboard controller from there
keyboardController = LocalSoftwareKeyboardController.current
WirePasswordTextField(
state = when {
!isPasswordValid -> WireTextFieldState.Error(stringResource(id = R.string.remove_device_invalid_password))
else -> WireTextFieldState.Default
},
value = backupPassword,
onValueChange = {
backupPassword = it
onPasswordChanged(it)
},
autofill = false,
keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
modifier = Modifier.padding(bottom = dimensions().spacing16x)
)
}
}
}

Expand All @@ -124,7 +132,15 @@ fun ForgotLockCodeResettingDeviceDialog() {
@Composable
fun PreviewForgotLockCodeResetDeviceDialog() {
WireTheme {
ForgotLockCodeResetDeviceDialog("Username", true, true, {}, {}, {})
ForgotLockCodeResetDeviceDialog("Username", false, true, true, {}, {}, {})
}
}

@PreviewMultipleThemes
@Composable
fun PreviewForgotLockCodeResetDeviceWithoutPasswordDialog() {
WireTheme {
ForgotLockCodeResetDeviceDialog("Username", true, true, true, {}, {}, {})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ fun ForgotLockCodeScreen(
if (dialogState.loading) ForgotLockCodeResettingDeviceDialog()
else ForgotLockCodeResetDeviceDialog(
username = dialogState.username,
isPasswordRequired = dialogState.passwordRequired,
isPasswordValid = dialogState.passwordValid,
isResetDeviceEnabled = dialogState.resetDeviceEnabled,
onPasswordChanged = viewModel::onPasswordChanged,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ sealed class ForgotLockCodeDialogState {
data class Visible(
val username: String,
val password: TextFieldValue = TextFieldValue(""),
val passwordRequired: Boolean = false,
val passwordValid: Boolean = true,
val resetDeviceEnabled: Boolean = true,
val resetDeviceEnabled: Boolean = false,
val loading: Boolean = false,
) : ForgotLockCodeDialogState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ import com.wire.kalium.logic.feature.session.GetAllSessionsResult
import com.wire.kalium.logic.feature.session.GetSessionsUseCase
import com.wire.kalium.logic.feature.user.GetSelfUserUseCase
import com.wire.kalium.logic.feature.user.IsPasswordRequiredUseCase
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.fold
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
Expand Down Expand Up @@ -88,12 +85,26 @@ class ForgotLockScreenViewModel @Inject constructor(
}

fun onPasswordChanged(password: TextFieldValue) {
updateIfDialogStateVisible { it.copy(password = password, resetDeviceEnabled = true) }
updateIfDialogStateVisible { it.copy(password = password, resetDeviceEnabled = password.text.isNotBlank()) }
}

fun onResetDevice() {
viewModelScope.launch {
state = state.copy(dialogState = ForgotLockCodeDialogState.Visible(username = getSelf().firstOrNull()?.name ?: ""))
state = when (val isPasswordRequiredResult = isPasswordRequired()) {
is IsPasswordRequiredUseCase.Result.Success -> {
state.copy(
dialogState = ForgotLockCodeDialogState.Visible(
username = getSelf().firstOrNull()?.name ?: "",
passwordRequired = isPasswordRequiredResult.value,
resetDeviceEnabled = !isPasswordRequiredResult.value,
)
)
}
is IsPasswordRequiredUseCase.Result.Failure -> {
appLogger.e("$TAG Failed to check if password is required when opening reset passcode dialog")
state.copy(error = isPasswordRequiredResult.cause)
}
}
}
}

Expand All @@ -110,60 +121,65 @@ class ForgotLockScreenViewModel @Inject constructor(
updateIfDialogStateVisible { it.copy(resetDeviceEnabled = false) }
viewModelScope.launch {
validatePasswordIfNeeded(dialogStateVisible.password.text)
.flatMapIfSuccess {
.flatMapIfSuccess { validatedPassword ->
updateIfDialogStateVisible { it.copy(loading = true) }
deleteCurrentClient(dialogStateVisible.password.text)
.flatMapIfSuccess { hardLogoutAllAccounts() }
deleteCurrentClient(validatedPassword)
}
.fold({ error ->
state = state.copy(error = error)
updateIfDialogStateVisible { it.copy(loading = false, resetDeviceEnabled = true) }
}, { result ->
.flatMapIfSuccess { hardLogoutAllAccounts() }
.let { result ->
when (result) {
Result.InvalidPassword -> updateIfDialogStateVisible { it.copy(passwordValid = false, loading = false) }
Result.Success -> state = state.copy(completed = true, dialogState = ForgotLockCodeDialogState.Hidden)
is Result.Failure.Generic -> {
state = state.copy(error = result.cause)
updateIfDialogStateVisible { it.copy(loading = false, resetDeviceEnabled = true) }
}
Result.Failure.PasswordRequired ->
updateIfDialogStateVisible { it.copy(passwordRequired = true, passwordValid = false, loading = false) }
Result.Failure.InvalidPassword ->
updateIfDialogStateVisible { it.copy(passwordValid = false, loading = false) }
Result.Success ->
state = state.copy(completed = true, dialogState = ForgotLockCodeDialogState.Hidden)
}
}
)
}
}
}

@VisibleForTesting
internal suspend fun validatePasswordIfNeeded(password: String): Either<CoreFailure, Result> =
internal suspend fun validatePasswordIfNeeded(password: String): Pair<Result, String> =
when (val isPasswordRequiredResult = isPasswordRequired()) {
is IsPasswordRequiredUseCase.Result.Failure -> {
appLogger.e("$TAG Failed to check if password is required when resetting passcode")
Either.Left(isPasswordRequiredResult.cause)
Result.Failure.Generic(isPasswordRequiredResult.cause) to password
}
is IsPasswordRequiredUseCase.Result.Success -> {
if (!isPasswordRequiredResult.value || validatePassword(password).isValid) Either.Right(Result.Success)
else Either.Right(Result.InvalidPassword)
is IsPasswordRequiredUseCase.Result.Success -> when {
isPasswordRequiredResult.value && password.isBlank() -> Result.Failure.PasswordRequired to password
isPasswordRequiredResult.value && !validatePassword(password).isValid -> Result.Failure.InvalidPassword to password
else -> Result.Success to if (isPasswordRequiredResult.value) password else ""
}
}

@VisibleForTesting
internal suspend fun deleteCurrentClient(password: String): Either<CoreFailure, Result> =
internal suspend fun deleteCurrentClient(password: String): Result =
observeCurrentClientId()
.filterNotNull()
.first()
.let { clientId ->
when (val deleteClientResult = deleteClient(DeleteClientParam(password, clientId))) {
is DeleteClientResult.Failure.Generic -> {
appLogger.e("$TAG Failed to delete current client when resetting passcode")
Either.Left(deleteClientResult.genericFailure)
Result.Failure.Generic(deleteClientResult.genericFailure)
}
DeleteClientResult.Success -> Either.Right(Result.Success)
else -> Either.Right(Result.InvalidPassword)
DeleteClientResult.Success -> Result.Success
else -> Result.Failure.InvalidPassword
}
}

@VisibleForTesting
internal suspend fun hardLogoutAllAccounts(): Either<CoreFailure, Result> =
internal suspend fun hardLogoutAllAccounts(): Result =
when (val getAllSessionsResult = getSessions()) {
is GetAllSessionsResult.Failure.Generic -> {
appLogger.e("$TAG Failed to get all sessions when resetting passcode")
Either.Left(getAllSessionsResult.genericFailure)
Result.Failure.Generic(getAllSessionsResult.genericFailure)
}
is GetAllSessionsResult.Failure.NoSessionFound,
is GetAllSessionsResult.Success -> {
Expand All @@ -178,7 +194,7 @@ class ForgotLockScreenViewModel @Inject constructor(
}.joinAll() // wait until all accounts are logged out
globalDataStore.clearAppLockPasscode()
accountSwitch(SwitchAccountParam.Clear)
Either.Right(Result.Success)
Result.Success
}
}

Expand All @@ -190,10 +206,20 @@ class ForgotLockScreenViewModel @Inject constructor(
userDataStoreProvider.getOrCreate(userId).clear()
}

internal enum class Result { InvalidPassword, Success; }
internal sealed class Result {
sealed class Failure : Result() {
data object InvalidPassword : Failure()
data object PasswordRequired : Failure()
data class Generic(val cause: CoreFailure) : Failure()
}
data object Success : Result()
}

private inline fun Result.flatMapIfSuccess(block: () -> Result): Result =
if (this is Result.Success) block() else this

private inline fun <T> Either<T, Result>.flatMapIfSuccess(block: () -> Either<T, Result>): Either<T, Result> =
this.flatMap { if (it == Result.Success) block() else Either.Right(it) }
private inline fun <T> Pair<Result, T>.flatMapIfSuccess(block: (T) -> Result): Result =
if (this.first is Result.Success) block(this.second) else this.first

companion object {
const val TAG = "ForgotLockResetPasscode"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,9 @@ fun EnabledMessageComposer(
}
}

BackHandler(inputStateHolder.inputType is MessageCompositionType.Editing) {
cancelEdit()
}
BackHandler(isImeVisible || inputStateHolder.optionsVisible) {
inputStateHolder.handleBackPressed(
isImeVisible,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ private fun InputContent(
onPlusClick: () -> Unit,
modifier: Modifier,
) {
if (!showOptions) {
if (!showOptions && inputType is MessageCompositionType.Composing) {
AdditionalOptionButton(
isSelected = false,
onClick = {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,7 @@
<string name="settings_forgot_lock_screen_warning">If you remove your device, you lose all local data and messages for all accounts on this device permanently.</string>
<string name="settings_forgot_lock_screen_reset_device">Remove Device</string>
<string name="settings_forgot_lock_screen_reset_device_description">Enter your password for the account %s to confirm the deletion of all data for all accounts on this device. After removing your device, you can log in with your account credentials.</string>
<string name="settings_forgot_lock_screen_reset_device_without_password_description">Confirm the deletion of all data for all accounts on this device. After removing your device, you can log in with your account credentials.</string>
<string name="settings_forgot_lock_screen_please_wait_label">Please wait...</string>
<!--Devices -->
<string name="devices_title">Your Devices</string>
Expand Down
Loading

0 comments on commit cd716b6

Please sign in to comment.