diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt index f923c1e4ac..135302b0a0 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt @@ -1,8 +1,7 @@ package com.x8bit.bitwarden.data.auth.manager -import android.content.Context -import android.widget.Toast import androidx.annotation.StringRes +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.data.manager.DispatcherManager import com.x8bit.bitwarden.R @@ -27,15 +26,15 @@ import timber.log.Timber */ @Suppress("LongParameterList") class UserLogoutManagerImpl( - private val context: Context, private val authDiskSource: AuthDiskSource, private val generatorDiskSource: GeneratorDiskSource, private val passwordHistoryDiskSource: PasswordHistoryDiskSource, private val pushDiskSource: PushDiskSource, private val settingsDiskSource: SettingsDiskSource, + private val toastManager: ToastManager, private val vaultDiskSource: VaultDiskSource, - dispatcherManager: DispatcherManager, private val vaultSdkSource: VaultSdkSource, + dispatcherManager: DispatcherManager, ) : UserLogoutManager { private val scope = CoroutineScope(dispatcherManager.unconfined) private val mainScope = CoroutineScope(dispatcherManager.main) @@ -117,7 +116,7 @@ class UserLogoutManagerImpl( } private fun showToast(@StringRes message: Int) { - mainScope.launch { Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } + mainScope.launch { toastManager.show(messageId = message) } } private fun switchUserIfAvailable( diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/di/AuthManagerModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/di/AuthManagerModule.kt index 87bdf25f7e..86bc26548b 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/di/AuthManagerModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/di/AuthManagerModule.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.auth.manager.di import android.content.Context +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.data.manager.DispatcherManager import com.bitwarden.network.service.AccountsService import com.bitwarden.network.service.AuthRequestsService @@ -107,23 +108,23 @@ object AuthManagerModule { @Provides @Singleton fun provideUserLogoutManager( - @ApplicationContext context: Context, authDiskSource: AuthDiskSource, generatorDiskSource: GeneratorDiskSource, passwordHistoryDiskSource: PasswordHistoryDiskSource, pushDiskSource: PushDiskSource, settingsDiskSource: SettingsDiskSource, + toastManager: ToastManager, vaultDiskSource: VaultDiskSource, vaultSdkSource: VaultSdkSource, dispatcherManager: DispatcherManager, ): UserLogoutManager = UserLogoutManagerImpl( - context = context, authDiskSource = authDiskSource, generatorDiskSource = generatorDiskSource, passwordHistoryDiskSource = passwordHistoryDiskSource, pushDiskSource = pushDiskSource, settingsDiskSource = settingsDiskSource, + toastManager = toastManager, vaultDiskSource = vaultDiskSource, vaultSdkSource = vaultSdkSource, dispatcherManager = dispatcherManager, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/di/AccessibilityModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/di/AccessibilityModule.kt index cfe11d45f4..90ff6b29eb 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/di/AccessibilityModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/di/AccessibilityModule.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.pm.PackageManager import android.os.PowerManager import android.view.accessibility.AccessibilityManager +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.data.manager.DispatcherManager import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl @@ -89,6 +90,7 @@ object AccessibilityModule { accessibilityAutofillManager: AccessibilityAutofillManager, launcherPackageNameManager: LauncherPackageNameManager, powerManager: PowerManager, + toastManager: ToastManager, ): BitwardenAccessibilityProcessor = BitwardenAccessibilityProcessorImpl( context = context, @@ -96,6 +98,7 @@ object AccessibilityModule { accessibilityAutofillManager = accessibilityAutofillManager, launcherPackageNameManager = launcherPackageNameManager, powerManager = powerManager, + toastManager = toastManager, ) @Singleton diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt index 12cadf3448..0f51fcb89c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt @@ -5,6 +5,7 @@ import android.os.PowerManager import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.widget.Toast +import com.bitwarden.core.data.manager.toast.ToastManager import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManager @@ -26,6 +27,7 @@ class BitwardenAccessibilityProcessorImpl( private val accessibilityAutofillManager: AccessibilityAutofillManager, private val launcherPackageNameManager: LauncherPackageNameManager, private val powerManager: PowerManager, + private val toastManager: ToastManager, ) : BitwardenAccessibilityProcessor { override fun processAccessibilityEvent( event: AccessibilityEvent, @@ -110,13 +112,10 @@ class BitwardenAccessibilityProcessorImpl( ) } ?: run { - Toast - .makeText( - context, - R.string.autofill_tile_uri_not_found, - Toast.LENGTH_LONG, - ) - .show() + toastManager.show( + messageId = R.string.autofill_tile_uri_not_found, + duration = Toast.LENGTH_LONG, + ) } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt index 6a61dbf401..d05fcd0ca9 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt @@ -93,7 +93,6 @@ object AutofillModule { @Singleton @Provides fun providesAutofillTotpManager( - @ApplicationContext context: Context, clock: Clock, clipboardManager: BitwardenClipboardManager, authRepository: AuthRepository, @@ -101,7 +100,6 @@ object AutofillModule { vaultRepository: VaultRepository, ): AutofillTotpManager = AutofillTotpManagerImpl( - context = context, clock = clock, clipboardManager = clipboardManager, authRepository = authRepository, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/AutofillTotpManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/AutofillTotpManagerImpl.kt index c3c8c8807e..bcad86dab9 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/AutofillTotpManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/AutofillTotpManagerImpl.kt @@ -1,7 +1,5 @@ package com.x8bit.bitwarden.data.autofill.manager -import android.content.Context -import android.widget.Toast import com.bitwarden.ui.util.asText import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.R @@ -16,7 +14,6 @@ import java.time.Clock * Default implementation of the [AutofillTotpManager]. */ class AutofillTotpManagerImpl( - private val context: Context, private val clock: Clock, private val clipboardManager: BitwardenClipboardManager, private val authRepository: AuthRepository, @@ -39,13 +36,6 @@ class AutofillTotpManagerImpl( text = totpResult.code, toastDescriptorOverride = R.string.verification_code_totp.asText(), ) - Toast - .makeText( - context.applicationContext, - R.string.verification_code_totp, - Toast.LENGTH_LONG, - ) - .show() } } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/clipboard/BitwardenClipboardManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/clipboard/BitwardenClipboardManagerImpl.kt index 0f227fb0f6..28bbb14e42 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/clipboard/BitwardenClipboardManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/clipboard/BitwardenClipboardManagerImpl.kt @@ -4,7 +4,6 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Build -import android.widget.Toast import androidx.compose.ui.text.AnnotatedString import androidx.core.content.getSystemService import androidx.core.os.persistableBundleOf @@ -12,6 +11,8 @@ import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import com.bitwarden.annotation.OmitFromCoverage +import com.bitwarden.core.data.manager.toast.ToastManager +import com.bitwarden.core.util.isBuildVersionAtLeast import com.bitwarden.ui.platform.base.util.toAnnotatedString import com.bitwarden.ui.util.Text import com.x8bit.bitwarden.R @@ -25,6 +26,7 @@ import java.util.concurrent.TimeUnit class BitwardenClipboardManagerImpl( private val context: Context, private val settingsRepository: SettingsRepository, + private val toastManager: ToastManager, ) : BitwardenClipboardManager { private val clipboardManager: ClipboardManager = requireNotNull(context.getSystemService()) @@ -45,14 +47,14 @@ class BitwardenClipboardManagerImpl( ) }, ) - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + if (isBuildVersionAtLeast(version = Build.VERSION_CODES.S_V2)) { val descriptor = toastDescriptorOverride ?.let { context.resources.getString(R.string.value_has_been_copied, it) } ?: context.resources.getString( R.string.value_has_been_copied, context.resources.getString(R.string.value), ) - Toast.makeText(context, descriptor, Toast.LENGTH_SHORT).show() + toastManager.show(message = descriptor) } val frequency = clearClipboardFrequencySeconds ?: return diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt index bf08aa3c70..9bf949bad6 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt @@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.platform.manager.di import android.app.Application import android.content.Context import androidx.core.content.getSystemService +import com.bitwarden.core.data.manager.toast.ToastManager +import com.bitwarden.core.data.manager.toast.ToastManagerImpl import com.bitwarden.data.manager.DispatcherManager import com.bitwarden.data.manager.DispatcherManagerImpl import com.bitwarden.data.repository.ServerConfigRepository @@ -189,9 +191,19 @@ object PlatformManagerModule { fun provideBitwardenClipboardManager( @ApplicationContext context: Context, settingsRepository: SettingsRepository, + toastManager: ToastManager, ): BitwardenClipboardManager = BitwardenClipboardManagerImpl( - context, - settingsRepository, + context = context, + settingsRepository = settingsRepository, + toastManager = toastManager, + ) + + @Provides + @Singleton + fun provideToastManager( + @ApplicationContext context: Context, + ): ToastManager = ToastManagerImpl( + context = context, ) @Provides diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalScreen.kt index 64d23fc8e3..9120604694 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalScreen.kt @@ -1,6 +1,5 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginapproval -import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -20,7 +19,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics @@ -57,16 +55,10 @@ fun LoginApprovalScreen( onNavigateBack: () -> Unit, ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() - val context = LocalContext.current - val resources = context.resources EventsEffect(viewModel = viewModel) { event -> when (event) { LoginApprovalEvent.ExitApp -> exitManager.exitApplication() LoginApprovalEvent.NavigateBack -> onNavigateBack() - - is LoginApprovalEvent.ShowToast -> { - Toast.makeText(context, event.message(resources), Toast.LENGTH_SHORT).show() - } } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModel.kt index fd90250fc0..2486f6b9dd 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModel.kt @@ -3,8 +3,10 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginapproval import android.os.Parcelable +import androidx.annotation.StringRes import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.core.data.util.toFormattedDateTimeStyle import com.bitwarden.ui.platform.base.BaseViewModel import com.bitwarden.ui.util.Text @@ -40,6 +42,7 @@ class LoginApprovalViewModel @Inject constructor( private val authRepository: AuthRepository, private val specialCircumstanceManager: SpecialCircumstanceManager, private val snackbarRelayManager: SnackbarRelayManager, + private val toastManager: ToastManager, savedStateHandle: SavedStateHandle, ) : BaseViewModel( initialState = savedStateHandle[KEY_STATE] @@ -186,7 +189,7 @@ class LoginApprovalViewModel @Inject constructor( ) { when (val result = action.result) { is AuthRequestResult.Success -> { - sendClosingEvent(message = R.string.login_approved.asText()) + sendClosingEvent(messageId = R.string.login_approved) } is AuthRequestResult.Error -> { @@ -249,7 +252,7 @@ class LoginApprovalViewModel @Inject constructor( ) { when (val result = action.result) { is AuthRequestResult.Success -> { - sendClosingEvent(message = R.string.log_in_denied.asText()) + sendClosingEvent(messageId = R.string.log_in_denied) } is AuthRequestResult.Error -> { @@ -266,15 +269,15 @@ class LoginApprovalViewModel @Inject constructor( } } - private fun sendClosingEvent(message: Text? = null) { + private fun sendClosingEvent(@StringRes messageId: Int? = null) { val shouldFinishWhenComplete = state.specialCircumstance?.shouldFinishWhenComplete == true - message?.let { + messageId?.let { if (shouldFinishWhenComplete) { // We are about to exit the app, so we need to use a Toast here. - sendEvent(LoginApprovalEvent.ShowToast(it)) + toastManager.show(messageId = it) } else { snackbarRelayManager.sendSnackbarData( - data = BitwardenSnackbarData(message = it), + data = BitwardenSnackbarData(message = it.asText()), relay = SnackbarRelay.LOGIN_APPROVAL, ) } @@ -372,13 +375,6 @@ sealed class LoginApprovalEvent { * Navigates back. */ data object NavigateBack : LoginApprovalEvent() - - /** - * Displays the [message] in a toast. - */ - data class ShowToast( - val message: Text, - ) : LoginApprovalEvent() } /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt index db37dafc2a..de52edc07a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt @@ -1,6 +1,5 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting -import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.ExperimentalMaterial3Api @@ -14,7 +13,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.core.net.toUri @@ -96,8 +94,6 @@ fun VaultItemListingScreen( viewModel: VaultItemListingViewModel = hiltViewModel(), ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() - val context = LocalContext.current - val resources = context.resources val userVerificationHandlers = remember(viewModel) { VaultItemListingUserVerificationHandlers.create(viewModel = viewModel) } @@ -122,10 +118,6 @@ fun VaultItemListingScreen( intentManager.shareText(event.content) } - is VaultItemListingEvent.ShowToast -> { - Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show() - } - is VaultItemListingEvent.NavigateToAddVaultItem -> { onNavigateToVaultAddItemScreen( VaultAddEditArgs( diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index af24bf14c7..b6cf668628 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -9,6 +9,7 @@ import androidx.credentials.provider.ProviderCreateCredentialRequest import androidx.credentials.provider.ProviderGetCredentialRequest import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.core.data.repository.model.DataState import com.bitwarden.core.data.repository.util.map import com.bitwarden.data.repository.util.baseIconUrl @@ -142,6 +143,7 @@ class VaultItemListingViewModel @Inject constructor( private val networkConnectionManager: NetworkConnectionManager, private val featureFlagManager: FeatureFlagManager, private val relyingPartyParser: RelyingPartyParser, + private val toastManager: ToastManager, snackbarRelayManager: SnackbarRelayManager, ) : BaseViewModel( initialState = run { @@ -1905,7 +1907,7 @@ class VaultItemListingViewModel @Inject constructor( is Fido2RegisterCredentialResult.Success -> { // This must be a toast because we are finishing the activity and we want the // user to have time to see the message. - sendEvent(VaultItemListingEvent.ShowToast(R.string.item_updated.asText())) + toastManager.show(messageId = R.string.item_updated) sendEvent( VaultItemListingEvent.CompleteFido2Registration( RegisterFido2CredentialResult.Success(action.result.responseJson), @@ -1920,7 +1922,7 @@ class VaultItemListingViewModel @Inject constructor( ) { // This must be a toast because we are finishing the activity and we want the // user to have time to see the message. - sendEvent(VaultItemListingEvent.ShowToast(R.string.an_error_has_occurred.asText())) + toastManager.show(messageId = R.string.an_error_has_occurred) sendEvent( VaultItemListingEvent.CompleteFido2Registration( RegisterFido2CredentialResult.Error( @@ -2888,13 +2890,6 @@ sealed class VaultItemListingEvent { */ data class ShowShareSheet(val content: String) : VaultItemListingEvent() - /** - * Show a toast with the given message. - * - * @property text the text to display. - */ - data class ShowToast(val text: Text) : VaultItemListingEvent() - /** * Show a snackbar to the user. */ diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt index 87ec18e0e5..a70fa996b3 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt @@ -1,8 +1,6 @@ package com.x8bit.bitwarden.data.auth.manager -import android.content.Context -import android.widget.Toast -import androidx.annotation.StringRes +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager import com.bitwarden.network.model.KdfTypeJson import com.bitwarden.ui.platform.base.MainDispatcherExtension @@ -24,11 +22,8 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.just import io.mockk.mockk -import io.mockk.mockkStatic import io.mockk.runs -import io.mockk.unmockkStatic import io.mockk.verify -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.time.ZonedDateTime @@ -60,26 +55,23 @@ class UserLogoutManagerTest { private val vaultSdkSource: VaultSdkSource = mockk { every { clearCrypto(userId = any()) } just runs } - private val context: Context = mockk() + private val toastManager: ToastManager = mockk { + every { show(messageId = any()) } just runs + } private val userLogoutManager: UserLogoutManager = UserLogoutManagerImpl( - context = context, authDiskSource = authDiskSource, generatorDiskSource = generatorDiskSource, passwordHistoryDiskSource = passwordHistoryDiskSource, pushDiskSource = pushDiskSource, settingsDiskSource = settingsDiskSource, + toastManager = toastManager, vaultDiskSource = vaultDiskSource, vaultSdkSource = vaultSdkSource, dispatcherManager = FakeDispatcherManager(), ) - @AfterEach - fun tearDown() { - unmockkStatic(Toast::class) - } - @Suppress("MaxLineLength") @Test fun `logout for single account should clear data associated with the given user and null out the user state`() { @@ -95,14 +87,15 @@ class UserLogoutManagerTest { @Suppress("MaxLineLength") @Test fun `logout for multiple accounts should clear data associated with the given user and change to the new active user`() { - mockToast(R.string.account_switched_automatically) - val userId = USER_ID_1 every { authDiskSource.userState } returns MULTI_USER_STATE userLogoutManager.logout(userId = USER_ID_1, reason = LogoutReason.Timeout) - verify { authDiskSource.userState = SINGLE_USER_STATE_2 } + verify { + authDiskSource.userState = SINGLE_USER_STATE_2 + toastManager.show(messageId = R.string.account_switched_automatically) + } assertDataCleared(userId = userId) } @@ -125,8 +118,6 @@ class UserLogoutManagerTest { val vaultTimeoutInMinutes = 360 val vaultTimeoutAction = VaultTimeoutAction.LOGOUT - mockToast(R.string.account_switched_automatically) - every { authDiskSource.userState } returns MULTI_USER_STATE every { settingsDiskSource.getVaultTimeoutInMinutes(userId = userId) @@ -149,9 +140,7 @@ class UserLogoutManagerTest { userId = userId, vaultTimeoutAction = vaultTimeoutAction, ) - Toast - .makeText(context, R.string.account_switched_automatically, Toast.LENGTH_SHORT) - .show() + toastManager.show(messageId = R.string.account_switched_automatically) } } @@ -161,8 +150,6 @@ class UserLogoutManagerTest { val vaultTimeoutInMinutes = 360 val vaultTimeoutAction = VaultTimeoutAction.LOGOUT - mockToast(R.string.account_switched_automatically) - every { authDiskSource.userState } returns MULTI_USER_STATE every { settingsDiskSource.getVaultTimeoutInMinutes(userId = userId) @@ -179,9 +166,7 @@ class UserLogoutManagerTest { activeUserId = USER_ID_2, accounts = MULTI_USER_STATE.accounts, ) - Toast - .makeText(context, R.string.account_switched_automatically, Toast.LENGTH_SHORT) - .show() + toastManager.show(messageId = R.string.account_switched_automatically) } } @@ -192,8 +177,6 @@ class UserLogoutManagerTest { val vaultTimeoutInMinutes = 360 val vaultTimeoutAction = VaultTimeoutAction.LOGOUT - mockToast(R.string.login_expired) - every { authDiskSource.userState } returns MULTI_USER_STATE every { settingsDiskSource.getVaultTimeoutInMinutes(userId = userId) @@ -210,7 +193,7 @@ class UserLogoutManagerTest { activeUserId = USER_ID_2, accounts = MULTI_USER_STATE.accounts, ) - Toast.makeText(context, R.string.login_expired, Toast.LENGTH_SHORT).show() + toastManager.show(messageId = R.string.login_expired) } } @@ -225,15 +208,6 @@ class UserLogoutManagerTest { vaultDiskSource.deleteVaultData(userId = userId) } } - - private fun mockToast(@StringRes res: Int) { - mockkStatic(Toast::class) - every { - Toast - .makeText(context, res, Toast.LENGTH_SHORT) - .show() - } just runs - } } private const val EMAIL_2 = "test2@bitwarden.com" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt index 1151e9b6da..075a047e27 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt @@ -6,6 +6,7 @@ import android.os.PowerManager import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.widget.Toast +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.vault.CipherView import com.bitwarden.vault.LoginView import com.x8bit.bitwarden.R @@ -41,6 +42,9 @@ class BitwardenAccessibilityProcessorTest { } private val launcherPackageNameManager: LauncherPackageNameManager = mockk() private val powerManager: PowerManager = mockk() + private val toastManager: ToastManager = mockk { + every { show(messageId = any(), duration = Toast.LENGTH_LONG) } just runs + } private val bitwardenAccessibilityProcessor: BitwardenAccessibilityProcessor = BitwardenAccessibilityProcessorImpl( @@ -49,6 +53,7 @@ class BitwardenAccessibilityProcessorTest { accessibilityAutofillManager = accessibilityAutofillManager, launcherPackageNameManager = launcherPackageNameManager, powerManager = powerManager, + toastManager = toastManager, ) @BeforeEach @@ -58,12 +63,6 @@ class BitwardenAccessibilityProcessorTest { AccessibilityNodeInfo::shouldSkipPackage, ) mockkStatic(::createAutofillSelectionIntent) - mockkStatic(Toast::class) - every { - Toast - .makeText(context, R.string.autofill_tile_uri_not_found, Toast.LENGTH_LONG) - .show() - } just runs } @AfterEach @@ -73,7 +72,6 @@ class BitwardenAccessibilityProcessorTest { AccessibilityNodeInfo::shouldSkipPackage, ) unmockkStatic(::createAutofillSelectionIntent) - unmockkStatic(Toast::class) } @Test @@ -252,9 +250,10 @@ class BitwardenAccessibilityProcessorTest { accessibilityAutofillManager.accessibilityAction accessibilityAutofillManager.accessibilityAction = null accessibilityParser.parseForUriOrPackageName(rootNode = node) - Toast - .makeText(context, R.string.autofill_tile_uri_not_found, Toast.LENGTH_LONG) - .show() + toastManager.show( + messageId = R.string.autofill_tile_uri_not_found, + duration = Toast.LENGTH_LONG, + ) } } @@ -305,9 +304,10 @@ class BitwardenAccessibilityProcessorTest { accessibilityAutofillManager.accessibilityAction = null accessibilityParser.parseForUriOrPackageName(rootNode = node) accessibilityParser.parseForFillableFields(rootNode = node, uri = uri) - Toast - .makeText(context, R.string.autofill_tile_uri_not_found, Toast.LENGTH_LONG) - .show() + toastManager.show( + messageId = R.string.autofill_tile_uri_not_found, + duration = Toast.LENGTH_LONG, + ) } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/AutofillTotpManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/AutofillTotpManagerTest.kt index 6319c97f80..30a3ad8719 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/AutofillTotpManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/AutofillTotpManagerTest.kt @@ -1,7 +1,5 @@ package com.x8bit.bitwarden.data.autofill.manager -import android.content.Context -import android.widget.Toast import com.bitwarden.ui.util.Text import com.bitwarden.ui.util.asText import com.bitwarden.vault.CipherView @@ -18,26 +16,16 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.just import io.mockk.mockk -import io.mockk.mockkStatic import io.mockk.runs -import io.mockk.unmockkStatic import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import java.time.Clock import java.time.Instant import java.time.ZoneOffset class AutofillTotpManagerTest { - private val context: Context = mockk { - every { applicationContext } returns this - } - private val toast: Toast = mockk { - every { show() } just runs - } private val loginView: LoginView = mockk() private val cipherView: CipherView = mockk { every { id } returns "cipherId" @@ -54,7 +42,6 @@ class AutofillTotpManagerTest { private val vaultRepository: VaultRepository = mockk() private val autofillTotpManager: AutofillTotpManager = AutofillTotpManagerImpl( - context = context, clock = FIXED_CLOCK, clipboardManager = clipboardManager, authRepository = authRepository, @@ -62,19 +49,6 @@ class AutofillTotpManagerTest { vaultRepository = vaultRepository, ) - @BeforeEach - fun setup() { - mockkStatic(Toast::class) - every { - Toast.makeText(context, R.string.verification_code_totp, Toast.LENGTH_LONG) - } returns toast - } - - @AfterEach - fun tearDown() { - unmockkStatic(Toast::class) - } - @Test fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is true should do nothing`() = runTest { every { settingsRepository.isAutoCopyTotpDisabled } returns true @@ -89,7 +63,6 @@ class AutofillTotpManagerTest { text = any(), toastDescriptorOverride = any(), ) - toast.show() } } @@ -110,7 +83,6 @@ class AutofillTotpManagerTest { text = any(), toastDescriptorOverride = any(), ) - toast.show() } verify(exactly = 1) { settingsRepository.isAutoCopyTotpDisabled @@ -135,7 +107,6 @@ class AutofillTotpManagerTest { text = any(), toastDescriptorOverride = any(), ) - toast.show() } verify(exactly = 1) { settingsRepository.isAutoCopyTotpDisabled @@ -144,7 +115,7 @@ class AutofillTotpManagerTest { @Suppress("MaxLineLength") @Test - fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is false, has premium and has totp should set the clipboard and toast`() = + fun `tryCopyTotpToClipboard when isAutoCopyTotpDisabled is false, has premium and has totp should set the clipboard`() = runTest { val generateTotpResult = GenerateTotpResult.Success( code = TOTP_RESULT_VALUE, @@ -168,7 +139,6 @@ class AutofillTotpManagerTest { toastDescriptorOverride = R.string.verification_code_totp.asText(), ) settingsRepository.isAutoCopyTotpDisabled - toast.show() } coVerify(exactly = 1) { vaultRepository.generateTotp(time = FIXED_CLOCK.instant(), totpCode = TOTP_CODE) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModelTest.kt index da4714291a..a56ef4992e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModelTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginap import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.data.repository.model.Environment import com.bitwarden.ui.platform.base.BaseViewModelTest @@ -63,6 +64,9 @@ class LoginApprovalViewModelTest : BaseViewModelTest() { private val snackbarRelayManager: SnackbarRelayManager = mockk { every { sendSnackbarData(data = any(), relay = SnackbarRelay.LOGIN_APPROVAL) } just runs } + private val toastManager: ToastManager = mockk { + every { show(messageId = any()) } just runs + } @BeforeEach fun setup() { @@ -327,12 +331,11 @@ class LoginApprovalViewModelTest : BaseViewModelTest() { viewModel.eventFlow.test { viewModel.trySendAction(LoginApprovalAction.ApproveRequestClick) - assertEquals( - LoginApprovalEvent.ShowToast(R.string.login_approved.asText()), - awaitItem(), - ) assertEquals(LoginApprovalEvent.ExitApp, awaitItem()) } + verify { + toastManager.show(messageId = R.string.login_approved) + } } @Test @@ -397,12 +400,11 @@ class LoginApprovalViewModelTest : BaseViewModelTest() { viewModel.eventFlow.test { viewModel.trySendAction(LoginApprovalAction.DeclineRequestClick) - assertEquals( - LoginApprovalEvent.ShowToast(R.string.log_in_denied.asText()), - awaitItem(), - ) assertEquals(LoginApprovalEvent.ExitApp, awaitItem()) } + verify { + toastManager.show(messageId = R.string.log_in_denied) + } } @Test @@ -441,6 +443,7 @@ class LoginApprovalViewModelTest : BaseViewModelTest() { authRepository = mockAuthRepository, specialCircumstanceManager = mockSpecialCircumstanceManager, snackbarRelayManager = snackbarRelayManager, + toastManager = toastManager, savedStateHandle = SavedStateHandle().apply { set("state", state) every { toLoginApprovalArgs() } returns LoginApprovalArgs(fingerprint = FINGERPRINT) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index 49c7916e65..a371715e35 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -14,6 +14,7 @@ import androidx.credentials.provider.ProviderGetCredentialRequest import androidx.credentials.provider.PublicKeyCredentialEntry import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.core.data.repository.model.DataState import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.core.data.util.asFailure @@ -159,6 +160,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { private val clipboardManager: BitwardenClipboardManager = mockk { every { setText(text = any(), toastDescriptorOverride = any()) } just runs } + private val toastManager: ToastManager = mockk { + every { show(messageId = any()) } just runs + } private val mutableUserStateFlow = MutableStateFlow(DEFAULT_USER_STATE) private val authRepository = mockk { @@ -3018,11 +3022,6 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ) viewModel.eventFlow.test { - assertEquals( - VaultItemListingEvent.ShowToast(R.string.an_error_has_occurred.asText()), - awaitItem(), - ) - assertEquals( VaultItemListingEvent.CompleteFido2Registration( RegisterFido2CredentialResult.Error( @@ -3032,6 +3031,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { awaitItem(), ) } + verify { + toastManager.show(messageId = R.string.an_error_has_occurred) + } } @Suppress("MaxLineLength") @@ -3050,11 +3052,6 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ) viewModel.eventFlow.test { - assertEquals( - VaultItemListingEvent.ShowToast(R.string.item_updated.asText()), - awaitItem(), - ) - assertEquals( VaultItemListingEvent.CompleteFido2Registration( RegisterFido2CredentialResult.Success( @@ -3064,6 +3061,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { awaitItem(), ) } + verify { + toastManager.show(messageId = R.string.item_updated) + } } @Suppress("MaxLineLength") @@ -5322,6 +5322,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { privilegedAppRepository = privilegedAppRepository, featureFlagManager = featureFlagManager, snackbarRelayManager = snackbarRelayManager, + toastManager = toastManager, relyingPartyParser = relyingPartyParser, ) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/clipboard/BitwardenClipboardManagerImpl.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/clipboard/BitwardenClipboardManagerImpl.kt index 40a0d7013c..8c5f809caf 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/clipboard/BitwardenClipboardManagerImpl.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/clipboard/BitwardenClipboardManagerImpl.kt @@ -4,11 +4,11 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Build -import android.widget.Toast import androidx.compose.ui.text.AnnotatedString import androidx.core.content.getSystemService import androidx.core.os.persistableBundleOf import com.bitwarden.authenticator.R +import com.bitwarden.core.data.manager.toast.ToastManager import com.bitwarden.ui.platform.base.util.toAnnotatedString import com.bitwarden.ui.util.Text @@ -17,6 +17,7 @@ import com.bitwarden.ui.util.Text */ class BitwardenClipboardManagerImpl( private val context: Context, + private val toastManager: ToastManager, ) : BitwardenClipboardManager { private val clipboardManager: ClipboardManager = requireNotNull(context.getSystemService()) @@ -35,14 +36,12 @@ class BitwardenClipboardManagerImpl( }, ) if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - val descriptor = toastDescriptorOverride ?: text - Toast - .makeText( - context, - context.resources.getString(R.string.value_has_been_copied, descriptor), - Toast.LENGTH_SHORT, - ) - .show() + toastManager.show( + message = context.resources.getString( + R.string.value_has_been_copied, + toastDescriptorOverride ?: text, + ), + ) } } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/di/PlatformManagerModule.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/di/PlatformManagerModule.kt index df1f3e5015..0ae8d708de 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/di/PlatformManagerModule.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/di/PlatformManagerModule.kt @@ -20,6 +20,8 @@ import com.bitwarden.authenticator.data.platform.manager.imports.ImportManager import com.bitwarden.authenticator.data.platform.manager.imports.ImportManagerImpl import com.bitwarden.authenticator.data.platform.repository.DebugMenuRepository import com.bitwarden.authenticator.data.platform.repository.SettingsRepository +import com.bitwarden.core.data.manager.toast.ToastManager +import com.bitwarden.core.data.manager.toast.ToastManagerImpl import com.bitwarden.data.manager.DispatcherManager import com.bitwarden.data.manager.DispatcherManagerImpl import com.bitwarden.data.repository.ServerConfigRepository @@ -41,7 +43,19 @@ object PlatformManagerModule { @Singleton fun provideBitwardenClipboardManager( @ApplicationContext context: Context, - ): BitwardenClipboardManager = BitwardenClipboardManagerImpl(context) + toastManager: ToastManager, + ): BitwardenClipboardManager = BitwardenClipboardManagerImpl( + context = context, + toastManager = toastManager, + ) + + @Provides + @Singleton + fun provideToastManager( + @ApplicationContext context: Context, + ): ToastManager = ToastManagerImpl( + context = context, + ) @Provides @Singleton diff --git a/core/src/main/kotlin/com/bitwarden/core/data/manager/toast/ToastManager.kt b/core/src/main/kotlin/com/bitwarden/core/data/manager/toast/ToastManager.kt new file mode 100644 index 0000000000..6815cec7ba --- /dev/null +++ b/core/src/main/kotlin/com/bitwarden/core/data/manager/toast/ToastManager.kt @@ -0,0 +1,19 @@ +package com.bitwarden.core.data.manager.toast + +import android.widget.Toast +import androidx.annotation.StringRes + +/** + * Wrapper class for displaying a [Toast]. + */ +interface ToastManager { + /** + * Displays a [Toast] with the given [message]. + */ + fun show(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) + + /** + * Displays a [Toast] with the given [messageId]. + */ + fun show(@StringRes messageId: Int, duration: Int = Toast.LENGTH_SHORT) +} diff --git a/core/src/main/kotlin/com/bitwarden/core/data/manager/toast/ToastManagerImpl.kt b/core/src/main/kotlin/com/bitwarden/core/data/manager/toast/ToastManagerImpl.kt new file mode 100644 index 0000000000..72b4257ecf --- /dev/null +++ b/core/src/main/kotlin/com/bitwarden/core/data/manager/toast/ToastManagerImpl.kt @@ -0,0 +1,19 @@ +package com.bitwarden.core.data.manager.toast + +import android.content.Context +import android.widget.Toast + +/** + * The default implementation of the [ToastManager]. + */ +class ToastManagerImpl( + private val context: Context, +) : ToastManager { + override fun show(message: CharSequence, duration: Int) { + Toast.makeText(context, message, duration).show() + } + + override fun show(messageId: Int, duration: Int) { + Toast.makeText(context, messageId, duration).show() + } +} diff --git a/core/src/test/kotlin/com/bitwarden/core/data/manager/toast/ToastManagerTest.kt b/core/src/test/kotlin/com/bitwarden/core/data/manager/toast/ToastManagerTest.kt new file mode 100644 index 0000000000..a622348e6e --- /dev/null +++ b/core/src/test/kotlin/com/bitwarden/core/data/manager/toast/ToastManagerTest.kt @@ -0,0 +1,68 @@ +package com.bitwarden.core.data.manager.toast + +import android.content.Context +import android.widget.Toast +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.runs +import io.mockk.unmockkStatic +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class ToastManagerTest { + private val context: Context = mockk() + private val toast: Toast = mockk { + every { show() } just runs + } + private val toastManager: ToastManager = ToastManagerImpl( + context = context, + ) + + @BeforeEach + fun setup() { + mockkStatic(Toast::class) + every { Toast.makeText(context, any(), any()) } returns toast + every { Toast.makeText(context, any(), any()) } returns toast + } + + @AfterEach + fun tearDown() { + unmockkStatic(Toast::class) + } + + @Test + fun `show with string should call show on Toast`() { + val message = "Test" + toastManager.show(message = message) + verify { + Toast.makeText(context, message, Toast.LENGTH_SHORT) + toast.show() + } + + toastManager.show(message = message, Toast.LENGTH_LONG) + verify { + Toast.makeText(context, message, Toast.LENGTH_LONG) + toast.show() + } + } + + @Test + fun `show with string resource should call show on Toast`() { + val messageId = 555 + toastManager.show(messageId = messageId) + verify { + Toast.makeText(context, messageId, Toast.LENGTH_SHORT) + toast.show() + } + + toastManager.show(messageId = messageId, Toast.LENGTH_LONG) + verify { + Toast.makeText(context, messageId, Toast.LENGTH_LONG) + toast.show() + } + } +}