diff --git a/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeResetDeviceDialog.kt b/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeResetDeviceDialog.kt deleted file mode 100644 index 2cef1c93afb..00000000000 --- a/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeResetDeviceDialog.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ -package com.wire.android.ui.home.appLock.forgot - -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.input.TextFieldState -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -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.ImeAction -import com.wire.android.R -import com.wire.android.ui.common.WireDialog -import com.wire.android.ui.common.WireDialogButtonProperties -import com.wire.android.ui.common.WireDialogButtonType -import com.wire.android.ui.common.button.WireButtonState -import com.wire.android.ui.common.colorsScheme -import com.wire.android.ui.common.dimensions -import com.wire.android.ui.common.textfield.DefaultPassword -import com.wire.android.ui.common.textfield.WirePasswordTextField -import com.wire.android.ui.common.textfield.WireTextFieldState -import com.wire.android.ui.common.wireDialogPropertiesBuilder -import com.wire.android.ui.theme.WireTheme -import com.wire.android.ui.theme.wireTypography -import com.wire.android.util.ui.PreviewMultipleThemes -import com.wire.android.util.ui.stringWithStyledArgs - -@Composable -fun ForgotLockCodeResetDeviceDialog( - passwordTextState: TextFieldState, - username: String, - isPasswordRequired: Boolean, - isPasswordValid: Boolean, - isResetDeviceEnabled: Boolean, - onResetDeviceClicked: () -> Unit, - onDialogDismissed: () -> Unit, - modifier: Modifier = Modifier, -) { - var keyboardController: SoftwareKeyboardController? = null - val onDialogDismissHideKeyboard: () -> Unit = { - keyboardController?.hide() - onDialogDismissed() - } - WireDialog( - modifier = modifier, - title = stringResource(R.string.settings_forgot_lock_screen_reset_device), - 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( - onClick = onDialogDismissHideKeyboard, - text = stringResource(id = R.string.label_cancel), - state = WireButtonState.Default - ), - optionButton1Properties = WireDialogButtonProperties( - onClick = { - keyboardController?.hide() - onResetDeviceClicked() - }, - text = stringResource(id = R.string.settings_forgot_lock_screen_reset_device), - type = WireDialogButtonType.Primary, - state = if (!isResetDeviceEnabled) WireButtonState.Disabled else WireButtonState.Error - ) - ) { - 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( - textState = passwordTextState, - state = when { - !isPasswordValid -> WireTextFieldState.Error(stringResource(id = R.string.remove_device_invalid_password)) - else -> WireTextFieldState.Default - }, - autoFill = false, - keyboardOptions = KeyboardOptions.DefaultPassword.copy(imeAction = ImeAction.Done), - onKeyboardAction = { keyboardController?.hide() }, - modifier = Modifier.padding(bottom = dimensions().spacing16x) - ) - } - } -} - -@Composable -fun ForgotLockCodeResettingDeviceDialog() { - WireDialog( - title = stringResource(R.string.settings_forgot_lock_screen_please_wait_label), - titleLoading = true, - properties = wireDialogPropertiesBuilder(dismissOnBackPress = false, dismissOnClickOutside = false), - onDismiss = {}, - ) -} - -@PreviewMultipleThemes -@Composable -fun PreviewForgotLockCodeResetDeviceDialog() { - WireTheme { - ForgotLockCodeResetDeviceDialog(TextFieldState(), "Username", false, true, true, {}, {}) - } -} - -@PreviewMultipleThemes -@Composable -fun PreviewForgotLockCodeResetDeviceWithoutPasswordDialog() { - WireTheme { - ForgotLockCodeResetDeviceDialog(TextFieldState(), "Username", true, true, true, {}, {}) - } -} - -@PreviewMultipleThemes -@Composable -fun PreviewForgotLockCodeResettingDeviceDialog() { - WireTheme { - ForgotLockCodeResettingDeviceDialog() - } -} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeScreen.kt index d3076f09cc3..e3de717450a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeScreen.kt @@ -17,6 +17,7 @@ */ package com.wire.android.ui.home.appLock.forgot +import android.content.Intent import androidx.compose.foundation.ScrollState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box @@ -47,11 +48,9 @@ import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.style.TextAlign import androidx.hilt.navigation.compose.hiltViewModel import com.wire.android.R -import com.wire.android.navigation.BackStackMode -import com.wire.android.navigation.LoginTypeSelector -import com.wire.android.navigation.NavigationCommand -import com.wire.android.navigation.Navigator import com.wire.android.navigation.annotation.app.WireDestination +import com.wire.android.ui.LocalActivity +import com.wire.android.ui.WireActivity import com.wire.android.ui.common.WireDialog import com.wire.android.ui.common.WireDialogButtonProperties import com.wire.android.ui.common.WireDialogButtonType @@ -59,46 +58,42 @@ import com.wire.android.ui.common.button.WireButtonState import com.wire.android.ui.common.button.WirePrimaryButton import com.wire.android.ui.common.rememberBottomBarElevationState import com.wire.android.ui.common.scaffold.WireScaffold -import com.wire.android.ui.destinations.NewLoginScreenDestination -import com.wire.android.ui.destinations.WelcomeScreenDestination +import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography +import com.wire.android.ui.userprofile.self.dialog.LogoutOptionsDialog +import com.wire.android.ui.userprofile.self.dialog.LogoutOptionsDialogState import com.wire.android.util.dialogErrorStrings import com.wire.android.util.ui.PreviewMultipleThemes @WireDestination @Composable fun ForgotLockCodeScreen( - navigator: Navigator, - loginTypeSelector: LoginTypeSelector, viewModel: ForgotLockScreenViewModel = hiltViewModel(), ) { + val activity = LocalActivity.current + val logoutOptionsDialogState = rememberVisibilityState() with(viewModel.state) { LaunchedEffect(completed) { - val destination = if (loginTypeSelector.canUseNewLogin()) NewLoginScreenDestination() else WelcomeScreenDestination() - if (completed) navigator.navigate(NavigationCommand(destination, BackStackMode.CLEAR_WHOLE)) + if (completed) { + startLoginActivity(activity) + } } ForgotLockCodeScreenContent( scrollState = rememberScrollState(), - onResetDevice = viewModel::onResetDevice, - ) - if (dialogState is ForgotLockCodeDialogState.Visible) { - if (dialogState.loading) { - ForgotLockCodeResettingDeviceDialog() - } else { - ForgotLockCodeResetDeviceDialog( - passwordTextState = viewModel.passwordTextState, - username = dialogState.username, - isPasswordRequired = dialogState.passwordRequired, - isPasswordValid = dialogState.passwordValid, - isResetDeviceEnabled = dialogState.resetDeviceEnabled, - onResetDeviceClicked = viewModel::onResetDeviceConfirmed, - onDialogDismissed = viewModel::onDialogDismissed, + isLoggingOut = isLoggingOut, + onLogout = { + logoutOptionsDialogState.show( + logoutOptionsDialogState.savedState ?: LogoutOptionsDialogState() ) - } - } + }, + ) + LogoutOptionsDialog( + dialogState = logoutOptionsDialogState, + logout = viewModel::onLogoutConfirmed, + ) if (error != null) { val (title, message) = error.dialogErrorStrings(LocalContext.current.resources) WireDialog( @@ -119,7 +114,8 @@ fun ForgotLockCodeScreen( @Composable fun ForgotLockCodeScreenContent( scrollState: ScrollState, - onResetDevice: () -> Unit, + isLoggingOut: Boolean, + onLogout: () -> Unit, modifier: Modifier = Modifier, ) { WireScaffold { internalPadding -> @@ -165,7 +161,7 @@ fun ForgotLockCodeScreenContent( text = stringResource(id = R.string.settings_forgot_lock_screen_warning), style = MaterialTheme.wireTypography.body01, textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.error, + color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.padding( top = MaterialTheme.wireDimensions.spacing8x, bottom = MaterialTheme.wireDimensions.spacing8x @@ -180,7 +176,7 @@ fun ForgotLockCodeScreenContent( modifier = Modifier.semantics { testTagsAsResourceId = true } ) { Box(modifier = Modifier.padding(MaterialTheme.wireDimensions.spacing16x)) { - ContinueButton(enabled = true, onContinue = onResetDevice) + LogoutButton(isLoggingOut = isLoggingOut, onLogout = onLogout) } } } @@ -188,29 +184,38 @@ fun ForgotLockCodeScreenContent( } @Composable -private fun ContinueButton( - enabled: Boolean, - onContinue: () -> Unit, +private fun LogoutButton( + isLoggingOut: Boolean, + onLogout: () -> Unit, modifier: Modifier = Modifier.fillMaxWidth(), ) { val interactionSource = remember { MutableInteractionSource() } Column(modifier = modifier) { WirePrimaryButton( - text = stringResource(R.string.settings_forgot_lock_screen_reset_device), - onClick = onContinue, - state = if (enabled) WireButtonState.Default else WireButtonState.Disabled, + text = stringResource(R.string.user_profile_logout), + onClick = onLogout, + loading = isLoggingOut, + state = if (isLoggingOut) WireButtonState.Disabled else WireButtonState.Default, interactionSource = interactionSource, modifier = Modifier .fillMaxWidth() - .testTag("reset_device_button") + .testTag("logout_button") ) } } +private fun startLoginActivity(activity: androidx.appcompat.app.AppCompatActivity) { + val intent = Intent(activity, WireActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + activity.startActivity(intent) + activity.finish() +} + @Composable @PreviewMultipleThemes fun PreviewForgotLockCodeScreen() { WireTheme { - ForgotLockCodeScreenContent(rememberScrollState(), {}) + ForgotLockCodeScreenContent(rememberScrollState(), false, {}) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeViewState.kt index cdfae4c7787..77c7d0503aa 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockCodeViewState.kt @@ -22,16 +22,5 @@ import com.wire.kalium.common.error.CoreFailure data class ForgotLockCodeViewState( val completed: Boolean = false, val error: CoreFailure? = null, - val dialogState: ForgotLockCodeDialogState = ForgotLockCodeDialogState.Hidden, + val isLoggingOut: Boolean = false, ) - -sealed class ForgotLockCodeDialogState { - data object Hidden : ForgotLockCodeDialogState() - data class Visible( - val username: String, - val passwordRequired: Boolean = false, - val passwordValid: Boolean = true, - val resetDeviceEnabled: Boolean = false, - val loading: Boolean = false, - ) : ForgotLockCodeDialogState() -} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockScreenViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockScreenViewModel.kt index 2a37fc5389d..635a9505a29 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockScreenViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockScreenViewModel.kt @@ -17,42 +17,26 @@ */ package com.wire.android.ui.home.appLock.forgot -import androidx.annotation.VisibleForTesting -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.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.wire.android.appLogger import com.wire.android.datastore.GlobalDataStore import com.wire.android.datastore.UserDataStoreProvider import com.wire.android.di.KaliumCoreLogic import com.wire.android.feature.AccountSwitchUseCase import com.wire.android.feature.SwitchAccountParam import com.wire.android.notification.WireNotificationManager -import com.wire.android.ui.common.textfield.textAsFlow -import com.wire.kalium.common.error.CoreFailure import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.auth.AccountInfo -import com.wire.kalium.logic.data.client.DeleteClientParam import com.wire.kalium.logic.data.logout.LogoutReason import com.wire.kalium.logic.data.user.UserId -import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase -import com.wire.kalium.logic.feature.client.DeleteClientResult -import com.wire.kalium.logic.feature.client.DeleteClientUseCase -import com.wire.kalium.logic.feature.client.ObserveCurrentClientIdUseCase 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 dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch @@ -63,141 +47,46 @@ import javax.inject.Inject class ForgotLockScreenViewModel @Inject constructor( @KaliumCoreLogic private val coreLogic: CoreLogic, private val globalDataStore: GlobalDataStore, - private val userDataStoreProvider: UserDataStoreProvider, private val notificationManager: WireNotificationManager, - private val getSelf: GetSelfUserUseCase, - private val isPasswordRequired: IsPasswordRequiredUseCase, - private val validatePassword: ValidatePasswordUseCase, - private val observeCurrentClientId: ObserveCurrentClientIdUseCase, - private val deleteClient: DeleteClientUseCase, + private val userDataStoreProvider: UserDataStoreProvider, private val getSessions: GetSessionsUseCase, private val observeEstablishedCalls: ObserveEstablishedCallsUseCase, private val endCall: EndCallUseCase, private val accountSwitch: AccountSwitchUseCase, ) : ViewModel() { - val passwordTextState: TextFieldState = TextFieldState() var state: ForgotLockCodeViewState by mutableStateOf(ForgotLockCodeViewState()) private set - init { - viewModelScope.launch { - passwordTextState.textAsFlow().collectLatest { newPassword -> - updateIfDialogStateVisible { it.copy(resetDeviceEnabled = newPassword.isNotBlank()) } - } - } - } - - private fun updateIfDialogStateVisible(update: (ForgotLockCodeDialogState.Visible) -> ForgotLockCodeDialogState) { - (state.dialogState as? ForgotLockCodeDialogState.Visible)?.let { dialogStateVisible -> - state = state.copy(dialogState = update(dialogStateVisible)) - } - } - - fun onResetDevice() { - passwordTextState.clearText() - viewModelScope.launch { - state = when (val isPasswordRequiredResult = isPasswordRequired()) { - is IsPasswordRequiredUseCase.Result.Success -> { - state.copy( - dialogState = ForgotLockCodeDialogState.Visible( - username = getSelf()?.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) - } - } - } - } - - fun onDialogDismissed() { - passwordTextState.clearText() - state = state.copy(dialogState = ForgotLockCodeDialogState.Hidden) - } - fun onErrorDismissed() { state = state.copy(error = null) } - fun onResetDeviceConfirmed() { - (state.dialogState as? ForgotLockCodeDialogState.Visible)?.let { dialogStateVisible -> - updateIfDialogStateVisible { it.copy(resetDeviceEnabled = false) } - viewModelScope.launch { - validatePasswordIfNeeded(passwordTextState.text.toString()) - .flatMapIfSuccess { validatedPassword -> - updateIfDialogStateVisible { it.copy(loading = true) } - deleteCurrentClient(validatedPassword) - } - .flatMapIfSuccess { hardLogoutAllAccounts() } - .let { result -> - when (result) { - 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) - } - } + fun onLogoutConfirmed(shouldWipeData: Boolean) { + viewModelScope.launch { + state = state.copy(isLoggingOut = true, error = null) + state = when (val result = logoutAllAccounts(shouldWipeData)) { + is Result.Failure.Generic -> state.copy(error = result.cause, isLoggingOut = false) + Result.Success -> { + state.copy(completed = true, isLoggingOut = false) + } } } } - @VisibleForTesting - internal suspend fun validatePasswordIfNeeded(password: String): Pair = - when (val isPasswordRequiredResult = isPasswordRequired()) { - is IsPasswordRequiredUseCase.Result.Failure -> { - appLogger.e("$TAG Failed to check if password is required when resetting passcode") - Result.Failure.Generic(isPasswordRequiredResult.cause) to password - } - 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): 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") - Result.Failure.Generic(deleteClientResult.genericFailure) - } - DeleteClientResult.Success -> Result.Success - else -> Result.Failure.InvalidPassword - } - } - - @VisibleForTesting - internal suspend fun hardLogoutAllAccounts(): Result = + private suspend fun logoutAllAccounts(shouldWipeData: Boolean): Result = when (val getAllSessionsResult = getSessions()) { - is GetAllSessionsResult.Failure.Generic -> { - appLogger.e("$TAG Failed to get all sessions when resetting passcode") - Result.Failure.Generic(getAllSessionsResult.genericFailure) - } + is GetAllSessionsResult.Failure.Generic -> Result.Failure.Generic(getAllSessionsResult.genericFailure) is GetAllSessionsResult.Failure.NoSessionFound, is GetAllSessionsResult.Success -> { observeEstablishedCalls().firstOrNull()?.let { establishedCalls -> establishedCalls.forEach { endCall(it.conversationId) } } val sessions = if (getAllSessionsResult is GetAllSessionsResult.Success) getAllSessionsResult.sessions else emptyList() + val logoutReason = if (shouldWipeData) LogoutReason.SELF_HARD_LOGOUT else LogoutReason.SELF_SOFT_LOGOUT sessions.filterIsInstance().map { session -> viewModelScope.launch { - hardLogoutAccount(session.userId) + logoutAccount(session.userId, logoutReason, shouldWipeData) } }.joinAll() // wait until all accounts are logged out globalDataStore.clearAppLockPasscode() @@ -206,29 +95,23 @@ class ForgotLockScreenViewModel @Inject constructor( } } - // TODO: we should have a dedicated manager to perform these required actions in AR after every LogoutUseCase call - private suspend fun hardLogoutAccount(userId: UserId) { + private suspend fun logoutAccount(userId: UserId, logoutReason: LogoutReason, shouldWipeData: Boolean) { notificationManager.stopObservingOnLogout(userId) - coreLogic.getSessionScope(userId).logout(reason = LogoutReason.SELF_HARD_LOGOUT, waitUntilCompletes = true) - userDataStoreProvider.getOrCreate(userId).clear() + coreLogic.getSessionScope(userId).logout(reason = logoutReason, waitUntilCompletes = true) + if (shouldWipeData) { + userDataStoreProvider.getOrCreate(userId).clear() + } } - internal sealed class Result { + private sealed class Result { sealed class Failure : Result() { - data object InvalidPassword : Failure() - data object PasswordRequired : Failure() - data class Generic(val cause: CoreFailure) : Failure() + data class Generic(val cause: com.wire.kalium.common.error.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 Pair.flatMapIfSuccess(block: (T) -> Result): Result = - if (this.first is Result.Success) block(this.second) else this.first - companion object { - const val TAG = "ForgotLockResetPasscode" + const val TAG = "ForgotLockLogout" } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index cd8e35bb164..0ba86731bef 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1193,11 +1193,6 @@ Bei Gruppenunterhaltungen kann der Gruppen-Admin diese Einstellung überschreibe Die App-Sperre ist jetzt verbindlich. Wire wird nach einer bestimmten Zeit der Inaktivität gesperrt. Um die App zu entsperren, müssen Sie ein Kennwort eingeben oder eine biometrische Authentifizierung verwenden. Die App-Sperre ist nicht mehr verbindlich. Wire wird nicht mehr nach einer bestimmten Zeit der Inaktivität gesperrt. Kennwort der App-Sperre vergessen? - Sie können auf die gespeicherten Daten auf diesem Gerät nur mit dem Kennwort für die App-Sperre zugreifen. Wenn Sie Ihr Kennwort vergessen haben, können Sie dieses Gerät entfernen. - Wenn Sie Ihr Gerät entfernen, verlieren Sie dauerhaft alle lokalen Daten und Nachrichten für alle Benutzerkonten auf diesem Gerät. - Gerät entfernen - Geben Sie Ihr Passwort für das Benutzerkonto %s ein, um die Löschung aller Daten für alle Konten auf diesem Gerät zu bestätigen. Nachdem Sie Ihr Gerät entfernt haben, können Sie sich mit Ihren Zugangsdaten anmelden. - Bestätigen Sie die Löschung aller Daten für alle Konten auf diesem Gerät. Nachdem Sie Ihr Gerät entfernt haben, können Sie sich mit Ihren Zugangsdaten anmelden. Bitte warten… Ihre Geräte diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index d2e7008655f..7862be063fb 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1117,11 +1117,6 @@ Ez a beállítás az összes beszélgetésre érvényes ezen az eszközön.Az alkalmazászár mostantól kötelező. A Wire egy bizonyos tétlenség után zárolja magát. Az alkalmazás feloldásához be kell írnia a jelkódot, vagy biometrikus azonosítást kell használnia. Az alkalmazászár többé nem kötelező. A Wire a jövőben nem zárolja magát bizonyos tétlenség után. Elfelejtette az alkalmazászár jelkódját? - Az ezen az eszközön tárolt adatokhoz csak az alkalmazászár jelkódjával tud hozzáférni. Ha elfelejtette a jelkódot, eltávolíthatja ezt az eszközt. - Ha eltávolítja ezt az eszközt, végérvényesen elveszíti minden fiók ezen az eszközön helyben tárolt összes adatát és üzenetét. - Eszköz eltávolítása - Adja meg a(z) %s fiók jelszavát, hogy jóváhagyja minden fiók ezen az eszközön tárolt összes adatának törlését. Az eszköz eltávolítása után a felhasználói azonosítóival jelentkezhet be. - Jóváhagyja minden fiók ezen az eszközön tárolt összes adatának törlését. Az eszköz eltávolítása után a felhasználói azonosítóival jelentkezhet be. Kérem, várjon… Saját eszközei diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ed5c72c0030..f80611c8a90 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -837,11 +837,6 @@ Rispondendo qui, verrà riagganciata l\'altra chiamata. Il blocco dell\'app è ora obbligatorio. Wire si bloccherà dopo un certo periodo di inattività. Per sbloccare l\'app, necessiti di inserire un codice d\'accesso, o di utilizzare l\'autenticazione biometrica. Il blocco dell\'app è ora obbligatorio. Wire si bloccherà dopo un certo periodo di inattività. Hai dimenticato il codice d\'accesso del blocco della tua app? - Puoi accedere ai dati memorizzati su questo dispositivo soltanto con il codice d\'accesso del blocco della tua app. Se lo hai dimenticato, puoi rimuovere questo dispositivo. - Se rimuovi il tuo dispositivo, perdi tutti i dati e messaggi locali per tutti i profili su questo dispositivo, permanentemente. - Rimuovi il dispositivo - Inserisci la tua password per il profilo %s per confermare l\'eliminazione di tutti i dati per tutti i profili su questo dispositivo. Dopo aver rimosso il tuo dispositivo, puoi accedere con le credenziali del tuo profilo. - Conferma l\'eliminazione di tutti i dati per tutti i profili su questo dispositivo. Dopo aver rimosso il tuo dispositivo, puoi accedere con le credenziali del tuo profilo. Ti preghiamo d\'attendere... I tuoi Dispositivi diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b7fe72d58ce..272b336b9fa 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1059,11 +1059,6 @@ Em conversas de grupo, o administrador do grupo pode substituir essa configuraç O bloqueio de apps agora é obrigatório. O Wire se bloqueará após um certo tempo de inatividade. Para desbloquear o aplicativo, você precisa digitar uma senha ou usar a Autenticação Biométrica. O bloqueio de apps não é mais obrigatório. O Wire não se bloqueará mais após um certo tempo de inatividade. Esqueceu a senha de bloqueio do aplicativo? - Você só pode acessar os dados armazenados neste dispositivo com sua senha de bloqueio. Se você esqueceu sua senha, pode remover este dispositivo. - Se você remover seu dispositivo, você perderá todos os dados e mensagens locais para todas as contas neste dispositivo permanentemente. - Remover o dispositivo - Digite sua senha para a conta %s para confirmar a exclusão de todos os dados para todas as contas neste dispositivo. Depois de remover o dispositivo, você poderá fazer login com as credenciais da sua conta. - Confirme a exclusão de todos os dados para todas as contas neste dispositivo. Após remover seu dispositivo, você pode fazer login com as credenciais da sua conta. Por favor, aguarde... Seus Dispositivos diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2dc8417c39a..995ea28f1f3 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1281,11 +1281,6 @@ Блокировка приложения теперь обязательна. Wire блокируется спустя определенное время бездействия. Для разблокировки приложения необходимо ввести код доступа или использовать биометрическую аутентификацию. Блокировка приложения больше не является обязательной. Wire больше не будет блокироваться сам по истечении определенного времени бездействия. Забыли код доступа к приложению? - Доступ к данным, хранящимся на этом устройстве, возможен только с помощью кода доступа приложения. Если вы забудете свой код доступа, вы можете удалить это устройство. - При удалении устройства вы навсегда теряете все локальные данные и сообщения всех аккаунтов на этом устройстве. - Удалить устройство - Введите пароль от аккаунта %s для подтверждения удаления всех данных для всех аккаунтов на этом устройстве. После удаления устройства вы cможете авторизоваться с использованием своих учетных данных. - Подтвердите удаление всех данных для всех аккаунтов на этом устройстве. После удаления устройства вы cможете авторизоваться с использованием своих учетных данных. Пожалуйста, подождите... Ваши устройства diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index a727912248e..78997a6acae 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -1130,11 +1130,6 @@ යෙදුමේ අගුල දැන් අනිවාර්යයි. නිශ්චිත නිෂ්ක්‍රියත්‍වයකින් පසුව වයර් යෙදුමට අගුළු වැටෙනු ඇත. යෙදුම අගුළු හැරීමට මුරකේතයක් හෝ වමිතික සත්‍යාපනය භාවිතා කළ යුතුය. යෙදුමේ අගුල තවදුරටත් අනිවාර්ය නොවේ. නිශ්චිත නිෂ්ක්‍රියත්‍වයකින් පසුව වයර් යෙදුමට අගුළු නොවැටෙනු ඇත. යෙදුමේ මුරකේතය අමතකද? - ඔබගේ යෙදුම අගුළු දමන මුර කේතයෙන් පමණක් මෙම උපාංගයේ ගබඩා කර ඇති දත්ත වෙත ප්‍රවේශ වීමට හැකිය. මුරකේතය අමතක වී ඇත්නම්, මෙම උපාංගය ඉවත් කිරීමට හැකිය. - ඔබ උපාංගය ඉවත් කළහොත්, මෙම උපාංගයේ ඇති සියලුම ගිණුම්වල ස්ථානීය දත්ත සහ පණිවිඩ සදහටම අහිමි වේ. - උපාංගය ඉවත් කරන්න - මෙම උපාංගයේ අඩංගු සියලුම ගිණුම්වල දත්ත මැකීමට %s ගිණුම වෙත මුරපදය ඇතුල් කරන්න. මෙම උපාංගය ඉවත් කිරීමෙන් පසු, ඔබට නැවත ගිණුමේ අක්තපත්‍ර සමඟින් පිවිසීමට හැකිය. - මෙම උපාංගයේ අඩංගු සියලුම ගිණුම්වල දත්ත මැකීමට තහවුරු කරන්න. ඔබගේ උපාංගය ඉවත් කිරීමෙන් පසු, ඔබට නැවත ගිණුමේ අක්තපත්‍ර සමඟින් පිවිසීමට හැකිය. කරුණාකර රැඳෙන්න... ඔබගේ උපාංග diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c229c4f0d4..c9d78750b27 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1287,11 +1287,8 @@ In group conversations, the group admin can overwrite this setting. App lock is now mandatory. Wire will lock itself after a certain time of inactivity. To unlock the app, you need to enter a passcode or use biometric authentication. App lock is not mandatory any more. Wire will no longer lock itself after a certain time of inactivity. Forgot your app lock passcode? - You can only access the data stored on this device with your app lock passcode. If you have forgotten your passcode, you can remove this device. - If you remove your device, you lose all local data and messages for all accounts on this device permanently. - Remove Device - 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. - Confirm the deletion of all data for all accounts on this device. After removing your device, you can log in with your account credentials. + The data stored on this device can only be accessed with your app lock passcode. + If you have forgotten your passcode, you can log out and set a new passcode the next time you log in. Your data will stay on this device unless you choose to delete it. Please wait... Your Devices diff --git a/app/src/test/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockScreenViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockScreenViewModelTest.kt index 54be5ac5f8b..0a8e2bcb918 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockScreenViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/appLock/forgot/ForgotLockScreenViewModelTest.kt @@ -18,8 +18,6 @@ package com.wire.android.ui.home.appLock.forgot import com.wire.android.config.CoroutineTestExtension -import com.wire.android.config.SnapshotExtension -import com.wire.android.config.TestDispatcherProvider import com.wire.android.datastore.GlobalDataStore import com.wire.android.datastore.UserDataStore import com.wire.android.datastore.UserDataStoreProvider @@ -27,34 +25,25 @@ import com.wire.android.feature.AccountSwitchUseCase import com.wire.android.feature.SwitchAccountParam import com.wire.android.feature.SwitchAccountResult import com.wire.android.notification.WireNotificationManager -import com.wire.kalium.logic.CoreLogic import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.auth.AccountInfo import com.wire.kalium.logic.data.call.Call -import com.wire.kalium.logic.data.conversation.ClientId import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.logout.LogoutReason import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.UserSessionScope import com.wire.kalium.logic.feature.auth.LogoutUseCase -import com.wire.kalium.logic.feature.auth.ValidatePasswordResult -import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase -import com.wire.kalium.logic.feature.client.DeleteClientResult -import com.wire.kalium.logic.feature.client.DeleteClientUseCase -import com.wire.kalium.logic.feature.client.ObserveCurrentClientIdUseCase 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 io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk -import io.mockk.verify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest @@ -62,201 +51,116 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -@ExtendWith(CoroutineTestExtension::class, SnapshotExtension::class) +@ExtendWith(CoroutineTestExtension::class) class ForgotLockScreenViewModelTest { - private val dispatcher = TestDispatcherProvider() - - // password validation - @Test - fun `given password not required, when validating password, then return Success with empty password`() = - runTest(dispatcher.default()) { - val (arrangement, viewModel) = Arrangement() - .withIsPasswordRequiredResult(IsPasswordRequiredUseCase.Result.Success(false)) - .arrange() - val (result, resultPassword) = viewModel.validatePasswordIfNeeded("password") - assert(result is ForgotLockScreenViewModel.Result.Success) - assertEquals("", resultPassword) - verify(exactly = 0) { arrangement.validatePasswordUseCase(any()) } - } - - @Test - fun `given password required and valid, when validating password, then return Success with given password`() = - runTest(dispatcher.default()) { - val (_, viewModel) = Arrangement() - .withIsPasswordRequiredResult(IsPasswordRequiredUseCase.Result.Success(true)) - .withValidatePasswordResult(ValidatePasswordResult.Valid) - .arrange() - val (result, resultPassword) = viewModel.validatePasswordIfNeeded("password") - assert(result is ForgotLockScreenViewModel.Result.Success) - assertEquals("password", resultPassword) - } - - @Test - fun `given password required but invalid, when validating password, then return InvalidPassword`() = - runTest(dispatcher.default()) { - val (_, viewModel) = Arrangement() - .withIsPasswordRequiredResult(IsPasswordRequiredUseCase.Result.Success(true)) - .withValidatePasswordResult(ValidatePasswordResult.Invalid()) - .arrange() - val (result, _) = viewModel.validatePasswordIfNeeded("password") - assert(result is ForgotLockScreenViewModel.Result.Failure.InvalidPassword) - } - - @Test - fun `given password required but not provided, when validating password, then return PasswordRequired`() = - runTest(dispatcher.default()) { - val (_, viewModel) = Arrangement() - .withIsPasswordRequiredResult(IsPasswordRequiredUseCase.Result.Success(true)) - .withValidatePasswordResult(ValidatePasswordResult.Invalid()) - .arrange() - val (result, _) = viewModel.validatePasswordIfNeeded("") - assert(result is ForgotLockScreenViewModel.Result.Failure.PasswordRequired) - } - - @Test - fun `given password required returns failure, when validating password, then return failure`() = - runTest(dispatcher.default()) { - val (arrangement, viewModel) = Arrangement() - .withIsPasswordRequiredResult(IsPasswordRequiredUseCase.Result.Failure(StorageFailure.DataNotFound)) - .arrange() - val (result, _) = viewModel.validatePasswordIfNeeded("password") - assert(result is ForgotLockScreenViewModel.Result.Failure) - verify(exactly = 0) { arrangement.validatePasswordUseCase(any()) } - } - - // current client deletion - private fun testClientDelete(deleteClientResult: DeleteClientResult, expected: ForgotLockScreenViewModel.Result) = - runTest(dispatcher.default()) { - val (_, viewModel) = Arrangement() - .withDeleteClientResult(deleteClientResult) - .arrange() - val result = viewModel.deleteCurrentClient("password") - assertEquals(expected, result) - } - @Test - fun `given deleting client returns success, when deleting current client, then return Success`() = - testClientDelete( - deleteClientResult = DeleteClientResult.Success, - expected = ForgotLockScreenViewModel.Result.Success - ) - - @Test - fun `given deleting client returns invalid credentials, when deleting current client, then return InvalidPassword`() = - testClientDelete( - deleteClientResult = DeleteClientResult.Failure.InvalidCredentials, - expected = ForgotLockScreenViewModel.Result.Failure.InvalidPassword - ) + fun `given sessions failure, when logout confirmed, then set error and do not clear app lock`() = runTest { + val (arrangement, viewModel) = Arrangement() + .withGetSessionsResult(GetAllSessionsResult.Failure.Generic(StorageFailure.DataNotFound)) + .arrange() + + viewModel.onLogoutConfirmed(shouldWipeData = false) + advanceUntilIdle() + + assertEquals(StorageFailure.DataNotFound, viewModel.state.error) + assertEquals(false, viewModel.state.completed) + assertEquals(false, viewModel.state.isLoggingOut) + coVerify(exactly = 0) { arrangement.globalDataStore.clearAppLockPasscode() } + coVerify(exactly = 0) { arrangement.accountSwitchUseCase(any()) } + } @Test - fun `given deleting client returns failure, when deleting current client, then return failure`() = - testClientDelete( - deleteClientResult = DeleteClientResult.Failure.Generic(StorageFailure.DataNotFound), - expected = ForgotLockScreenViewModel.Result.Failure.Generic(StorageFailure.DataNotFound) - ) - - // sessions hard logout - private fun Arrangement.verifyHardLogoutActions(successActionsCalled: Boolean, userLogoutActionsCalled: Boolean) { - val successActionsCalledExactly = if (successActionsCalled) 1 else 0 - coVerify(exactly = successActionsCalledExactly) { observeEstablishedCallsUseCase() } - coVerify(exactly = successActionsCalledExactly) { endCallUseCase(any()) } - coVerify(exactly = successActionsCalledExactly) { globalDataStore.clearAppLockPasscode() } - coVerify(exactly = successActionsCalledExactly) { accountSwitchUseCase(SwitchAccountParam.Clear) } - val logoutActionsCalledExactly = if (userLogoutActionsCalled) 1 else 0 - coVerify(exactly = logoutActionsCalledExactly) { logoutUseCase(any(), any()) } - coVerify(exactly = logoutActionsCalledExactly) { notificationManager.stopObservingOnLogout(any()) } - coVerify(exactly = logoutActionsCalledExactly) { userDataStore.clear() } - } - private fun testLoggingOut( - getSessionsResult: GetAllSessionsResult, - expected: ForgotLockScreenViewModel.Result, - successActionsCalled: Boolean, - userLogoutActionsCalled: Boolean - ) = - runTest(dispatcher.default()) { - val (arrangement, viewModel) = Arrangement() - .withGetSessionsResult(getSessionsResult) - .arrange() - val result = viewModel.hardLogoutAllAccounts() - advanceUntilIdle() - assertEquals(expected, result) - arrangement.verifyHardLogoutActions(successActionsCalled, userLogoutActionsCalled) + fun `given valid session and soft logout, when logout confirmed, then complete and keep user data`() = runTest { + val (arrangement, viewModel) = Arrangement() + .withGetSessionsResult(GetAllSessionsResult.Success(listOf(VALID_SESSION))) + .arrange() + + viewModel.onLogoutConfirmed(shouldWipeData = false) + advanceUntilIdle() + + assertEquals(true, viewModel.state.completed) + assertEquals(null, viewModel.state.error) + assertEquals(false, viewModel.state.isLoggingOut) + coVerify { arrangement.logoutUseCase(LogoutReason.SELF_SOFT_LOGOUT, true) } + coVerify { arrangement.notificationManager.stopObservingOnLogout(VALID_USER_ID) } + coVerify { arrangement.globalDataStore.clearAppLockPasscode() } + coVerify { arrangement.accountSwitchUseCase(SwitchAccountParam.Clear) } + coVerify(exactly = 0) { arrangement.userDataStore.clear() } } @Test - fun `given no sessions, when logging out, then make all required actions other than logout and return success`() = - testLoggingOut( - getSessionsResult = GetAllSessionsResult.Failure.NoSessionFound, - expected = ForgotLockScreenViewModel.Result.Success, - successActionsCalled = true, - userLogoutActionsCalled = false - ) + fun `given valid session and hard logout, when logout confirmed, then clear user data`() = runTest { + val (arrangement, viewModel) = Arrangement() + .withGetSessionsResult(GetAllSessionsResult.Success(listOf(VALID_SESSION))) + .arrange() - @Test - fun `given no valid sessions, when logging out, then make all required actions other than logout and return success`() = - testLoggingOut( - getSessionsResult = GetAllSessionsResult.Success(listOf(INVALID_SESSION)), - expected = ForgotLockScreenViewModel.Result.Success, - successActionsCalled = true, - userLogoutActionsCalled = false - ) + viewModel.onLogoutConfirmed(shouldWipeData = true) + advanceUntilIdle() - @Test - fun `given valid sessions, when logging out, then make all required actions with logout and return success`() = - testLoggingOut( - getSessionsResult = GetAllSessionsResult.Success(listOf(VALID_SESSION)), - expected = ForgotLockScreenViewModel.Result.Success, - successActionsCalled = true, - userLogoutActionsCalled = true - ) + assertEquals(true, viewModel.state.completed) + coVerify { arrangement.logoutUseCase(LogoutReason.SELF_HARD_LOGOUT, true) } + coVerify { arrangement.userDataStore.clear() } + } @Test - fun `given sessions return failure, when hard-logging out sessions, then return failure`() = - testLoggingOut( - getSessionsResult = GetAllSessionsResult.Failure.Generic(StorageFailure.DataNotFound), - expected = ForgotLockScreenViewModel.Result.Failure.Generic(StorageFailure.DataNotFound), - successActionsCalled = false, - userLogoutActionsCalled = false - ) + fun `given no sessions, when logout confirmed, then still complete and clear global app lock`() = runTest { + val (arrangement, viewModel) = Arrangement() + .withGetSessionsResult(GetAllSessionsResult.Failure.NoSessionFound) + .arrange() + + viewModel.onLogoutConfirmed(shouldWipeData = false) + advanceUntilIdle() + + assertEquals(true, viewModel.state.completed) + coVerify { arrangement.globalDataStore.clearAppLockPasscode() } + coVerify { arrangement.accountSwitchUseCase(SwitchAccountParam.Clear) } + coVerify(exactly = 0) { arrangement.logoutUseCase(any(), any()) } + } class Arrangement { - @MockK lateinit var coreLogic: CoreLogic - - @MockK lateinit var userSessionScope: UserSessionScope + @MockK + lateinit var coreLogic: CoreLogic - @MockK lateinit var logoutUseCase: LogoutUseCase + @MockK + lateinit var userSessionScope: UserSessionScope - @MockK lateinit var globalDataStore: GlobalDataStore + @MockK + lateinit var logoutUseCase: LogoutUseCase - @MockK lateinit var userDataStoreProvider: UserDataStoreProvider + @MockK + lateinit var globalDataStore: GlobalDataStore - @MockK lateinit var userDataStore: UserDataStore + @MockK + lateinit var userDataStoreProvider: UserDataStoreProvider - @MockK lateinit var notificationManager: WireNotificationManager + @MockK + lateinit var userDataStore: UserDataStore - @MockK lateinit var getSelfUserUseCase: GetSelfUserUseCase + @MockK + lateinit var notificationManager: WireNotificationManager - @MockK lateinit var isPasswordRequiredUseCase: IsPasswordRequiredUseCase + @MockK + lateinit var getSessionsUseCase: GetSessionsUseCase - @MockK lateinit var validatePasswordUseCase: ValidatePasswordUseCase + @MockK + lateinit var observeEstablishedCallsUseCase: ObserveEstablishedCallsUseCase - @MockK lateinit var observeCurrentClientIdUseCase: ObserveCurrentClientIdUseCase + @MockK + lateinit var endCallUseCase: EndCallUseCase - @MockK lateinit var deleteClientUseCase: DeleteClientUseCase - - @MockK lateinit var getSessionsUseCase: GetSessionsUseCase - - @MockK lateinit var observeEstablishedCallsUseCase: ObserveEstablishedCallsUseCase - - @MockK lateinit var endCallUseCase: EndCallUseCase - - @MockK lateinit var accountSwitchUseCase: AccountSwitchUseCase + @MockK + lateinit var accountSwitchUseCase: AccountSwitchUseCase private val viewModel: ForgotLockScreenViewModel by lazy { ForgotLockScreenViewModel( - coreLogic, globalDataStore, userDataStoreProvider, notificationManager, getSelfUserUseCase, - isPasswordRequiredUseCase, validatePasswordUseCase, observeCurrentClientIdUseCase, deleteClientUseCase, getSessionsUseCase, - observeEstablishedCallsUseCase, endCallUseCase, accountSwitchUseCase + coreLogic = coreLogic, + globalDataStore = globalDataStore, + notificationManager = notificationManager, + userDataStoreProvider = userDataStoreProvider, + getSessions = getSessionsUseCase, + observeEstablishedCalls = observeEstablishedCallsUseCase, + endCall = endCallUseCase, + accountSwitch = accountSwitchUseCase, ) } @@ -265,26 +169,25 @@ class ForgotLockScreenViewModelTest { every { coreLogic.getSessionScope(any()) } returns userSessionScope every { userSessionScope.logout } returns logoutUseCase every { userDataStoreProvider.getOrCreate(any()) } returns userDataStore - coEvery { observeCurrentClientIdUseCase() } returns flowOf(ClientId("currentClientId")) - val call: Call = mockk() - coEvery { observeEstablishedCallsUseCase() } returns flowOf(listOf(call)) - every { call.conversationId } returns ConversationId("conversationId", "domain") + coEvery { observeEstablishedCallsUseCase() } returns flowOf(listOf(mockCall())) coEvery { accountSwitchUseCase(any()) } returns SwitchAccountResult.NoOtherAccountToSwitch } - fun withIsPasswordRequiredResult(result: IsPasswordRequiredUseCase.Result) = - apply { coEvery { isPasswordRequiredUseCase() } returns result } - fun withValidatePasswordResult(result: ValidatePasswordResult) = - apply { coEvery { validatePasswordUseCase(any()) } returns result } - fun withDeleteClientResult(result: DeleteClientResult) = - apply { coEvery { deleteClientUseCase(any()) } returns result } fun withGetSessionsResult(result: GetAllSessionsResult) = apply { coEvery { getSessionsUseCase() } returns result } + fun arrange() = this to viewModel + + private fun mockCall(): Call { + val call: Call = mockk() + every { call.conversationId } returns CONVERSATION_ID + return call + } } companion object { - val INVALID_SESSION = AccountInfo.Invalid(UserId("id", "domain"), LogoutReason.SELF_HARD_LOGOUT) - val VALID_SESSION = AccountInfo.Valid(UserId("id", "domain")) + private val VALID_USER_ID = UserId("id", "domain") + private val VALID_SESSION = AccountInfo.Valid(VALID_USER_ID) + private val CONVERSATION_ID = ConversationId("conversation-id", "domain") } }