diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt index f4b57205f2..f05f11ef96 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt @@ -5,7 +5,6 @@ import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.updateTransition import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -275,7 +274,6 @@ private fun AnimatedAccountSwitcher( } @Suppress("LongMethod") -@OptIn(ExperimentalFoundationApi::class) @Composable private fun AccountSummaryItem( accountSummary: AccountSummary, @@ -471,8 +469,7 @@ private fun BitwardenAccountSwitcher_preview() { onLogoutAccountClick = {}, onAddAccountClick = {}, onDismissRequest = {}, - topAppBarScrollBehavior = - TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + topAppBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( state = rememberTopAppBarState(), canScroll = { false }, ), diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/bottomsheet/BitwardenModalBottomSheet.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/bottomsheet/BitwardenModalBottomSheet.kt index 5d6613866e..ca38eb6897 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/bottomsheet/BitwardenModalBottomSheet.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/bottomsheet/BitwardenModalBottomSheet.kt @@ -13,6 +13,9 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.SemanticsPropertyKey +import androidx.compose.ui.semantics.SemanticsPropertyReceiver +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar import com.bitwarden.ui.platform.components.appbar.NavigationIcon @@ -22,6 +25,7 @@ import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.platform.theme.BitwardenTheme import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import kotlinx.coroutines.launch +import org.jetbrains.annotations.VisibleForTesting /** * A reusable modal bottom sheet that applies provides a bottom sheet layout with the @@ -53,7 +57,7 @@ fun BitwardenModalBottomSheet( if (!showBottomSheet) return ModalBottomSheet( onDismissRequest = onDismiss, - modifier = modifier, + modifier = modifier.semantics { this.IsBottomSheet = true }, dragHandle = null, sheetState = sheetState, contentWindowInsets = { @@ -88,6 +92,13 @@ fun BitwardenModalBottomSheet( } } +/** + * SemanticPropertyKey used for Unit tests where checking if the content is part of a bottom sheet. + */ +@VisibleForTesting +val IsBottomSheetKey = SemanticsPropertyKey("IsBottomSheet") +private var SemanticsPropertyReceiver.IsBottomSheet by IsBottomSheetKey + @OptIn(ExperimentalMaterial3Api::class) @Composable private fun SheetState.createAnimatedDismissAction(onDismiss: () -> Unit): () -> Unit { diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/util/ComposeTextHelper.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/util/ComposeTextHelper.kt index 3b2a8c6a9d..27948c1022 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/util/ComposeTextHelper.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/util/ComposeTextHelper.kt @@ -2,8 +2,17 @@ package com.x8bit.bitwarden.ui.util import androidx.compose.ui.semantics.getOrNull import androidx.compose.ui.test.SemanticsMatcher +import com.x8bit.bitwarden.ui.platform.components.bottomsheet.IsBottomSheetKey import com.x8bit.bitwarden.ui.platform.components.coachmark.IsCoachMarkToolTipKey +/** + * A [SemanticsMatcher] user to find nodes used specifically inside a bottom sheet. + */ +val isBottomSheet: SemanticsMatcher + get() = SemanticsMatcher("Node is used to to indicate it is a bottom sheet.") { + it.config.getOrNull(IsBottomSheetKey) == true + } + /** * A [SemanticsMatcher] user to find Popup nodes used specifically for CoachMarkToolTips */ diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index c423fd49c6..1ff737c758 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -60,6 +60,7 @@ import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode +import com.x8bit.bitwarden.ui.util.isBottomSheet import com.x8bit.bitwarden.ui.util.isCoachMarkToolTip import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType @@ -2608,9 +2609,9 @@ class VaultAddEditScreenTest : BitwardenComposeTest() { composeTestRule .onAllNodesWithContentDescription("Close") - .filterToOne(hasAnySibling(hasText("Owner"))) + .filterToOne(hasAnyAncestor(isBottomSheet)) .assertIsDisplayed() - .performSemanticsAction(SemanticsActions.OnClick) + .performClick() dispatcher.advanceTimeByAndRunCurrent(1000L) @@ -2644,9 +2645,9 @@ class VaultAddEditScreenTest : BitwardenComposeTest() { composeTestRule .onAllNodesWithText("Save") - .filterToOne(hasAnySibling(hasText("Owner"))) + .filterToOne(hasAnyAncestor(isBottomSheet)) .assertIsDisplayed() - .performSemanticsAction(SemanticsActions.OnClick) + .performClick() verify { viewModel.trySendAction(VaultAddEditAction.Common.OwnershipChange(ownerId = ownerId)) @@ -2827,9 +2828,9 @@ class VaultAddEditScreenTest : BitwardenComposeTest() { composeTestRule .onAllNodesWithContentDescription("Close") - .filterToOne(hasAnySibling(hasText("Folders"))) + .filterToOne(hasAnyAncestor(isBottomSheet)) .assertIsDisplayed() - .performSemanticsAction(SemanticsActions.OnClick) + .performClick() dispatcher.advanceTimeByAndRunCurrent(1000L) @@ -2886,9 +2887,9 @@ class VaultAddEditScreenTest : BitwardenComposeTest() { composeTestRule .onAllNodesWithText("Save") - .filterToOne(hasAnySibling(hasText("Folders"))) + .filterToOne(hasAnyAncestor(isBottomSheet)) .assertIsDisplayed() - .performSemanticsAction(SemanticsActions.OnClick) + .performClick() verify { viewModel.trySendAction(VaultAddEditAction.Common.AddNewFolder(newFolderName)) @@ -2919,9 +2920,9 @@ class VaultAddEditScreenTest : BitwardenComposeTest() { composeTestRule .onAllNodesWithText("Save") - .filterToOne(hasAnySibling(hasText("Folders"))) + .filterToOne(hasAnyAncestor(isBottomSheet)) .assertIsDisplayed() - .performSemanticsAction(SemanticsActions.OnClick) + .performClick() verify { viewModel.trySendAction(VaultAddEditAction.Common.FolderChange(folderId = folderId)) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/importlogins/ImportLoginsScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/importlogins/ImportLoginsScreenTest.kt index faa59b9bc6..65ff1b186e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/importlogins/ImportLoginsScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/importlogins/ImportLoginsScreenTest.kt @@ -5,8 +5,6 @@ import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.filterToOne import androidx.compose.ui.test.hasAnyAncestor -import androidx.compose.ui.test.hasAnySibling -import androidx.compose.ui.test.hasText import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.onAllNodesWithContentDescription import androidx.compose.ui.test.onAllNodesWithText @@ -23,6 +21,7 @@ import com.bitwarden.ui.util.assertNoDialogExists import com.x8bit.bitwarden.data.util.advanceTimeByAndRunCurrent import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager +import com.x8bit.bitwarden.ui.util.isBottomSheet import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -468,9 +467,9 @@ class ImportLoginsScreenTest : BitwardenComposeTest() { composeTestRule .onAllNodesWithContentDescription("Close") - .filterToOne(hasAnySibling(hasText("Bitwarden Tools"))) + .filterToOne(hasAnyAncestor(isBottomSheet)) .assertIsDisplayed() - .performSemanticsAction(SemanticsActions.OnClick) + .performClick() dispatcher.advanceTimeByAndRunCurrent(1000L) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt index 364ec30378..e3937d7cd0 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt @@ -1,6 +1,5 @@ package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -56,7 +55,6 @@ import com.bitwarden.ui.platform.resource.BitwardenString * @param showMoveToBitwarden Whether the option to move the item to Bitwarden is displayed. * @param modifier The modifier for the item. */ -@OptIn(ExperimentalFoundationApi::class) @Suppress("LongMethod", "MagicNumber") @Composable fun VaultVerificationCodeItem( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f09f7ec7dc..b7d8e25b66 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,8 +19,8 @@ androdixAutofill = "1.3.0" androidxBiometrics = "1.2.0-alpha05" androidxBrowser = "1.9.0" androidxCamera = "1.4.2" -androidxComposeBom = "2025.07.00" -androidxCore = "1.16.0" +androidxComposeBom = "2025.08.00" +androidxCore = "1.17.0" androidxCredentials = "1.5.0" androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.9.2" @@ -30,9 +30,9 @@ androidxSecurityCrypto = "1.1.0" androidxSplash = "1.1.0-rc01" androidxWork = "2.10.3" bitwardenSdk = "1.0.0-2450-9fe3aeda" -crashlytics = "3.0.5" +crashlytics = "3.0.6" detekt = "1.23.8" -firebaseBom = "34.0.0" +firebaseBom = "34.1.0" glide = "1.0.0-beta01" googleGuava = "33.4.8-jre" googleProtoBufJava = "4.31.1"